Chapter 1: Introduction to Data Structures and Algorithms
1.07Beginner10 topics16 min2 interactive animations

Problem-solving workflow

A repeatable ten-step workflow for any algorithm problem: understand, make examples, write a brute-force baseline, find the pattern, pick the structure, prove, analyse, implement, test and optimise. The animations show a brute-force solution and its optimised replacement side by side.

Learning goals

  • Follow a fixed workflow instead of guessing
  • Use a brute-force solution as a baseline and test oracle
  • Justify a design with a short proof and a complexity analysis
  • Test and optimise methodically

Prerequisites

Choosing a solution
1.07.1

Understand requirements

Restate the problem in your own words. Identify the input, the output, the constraints and anything ambiguous: are duplicates allowed, is the answer unique, what if there is no answer? Ask or decide, and write the decision down.

Most wasted time comes from solving a slightly different problem than the one asked.

1.07.2

Build examples

Create small examples by hand, including the typical case, the smallest case and an awkward case. Work out the expected answers yourself. These become your tests and often reveal the pattern.

For two-sum with target 13: [7, 11, 2, 15, 4, 9] returns indexes of 11 and 2; [1, 2] with target 10 has no answer; [3, 3] with target 6 uses two equal values at different indexes.

1.07.3

Brute-force baseline

Write the simplest correct solution first, whatever its speed. It proves you understand the problem, gives a correct answer for small inputs, and later serves as an oracle to test the fast solution against.

Check every pair for two-sum: obviously correct, O(n²). Watch how many pairs it examines, including the worst case where no pair works.

Brute force: check every pair

Interactive · edit the input

Your input

Try

int[] arr · length 6

7
11
2
15
4
9
012345
Being comparedMatch / answerOuter index
Setup

Try every pair (i, j) with i < j. There are n(n-1)/2 = 15 of them in the worst case.

Step 1 / 10
Java
1public static int[] twoSum(int[] nums, int target) {
2 for (int i = 0; i < nums.length; i++) {
3 for (int j = i + 1; j < nums.length; j++) {
4 if (nums[i] + nums[j] == target) {
5 return new int[] { i, j };
6 }
7 }
8 }
9 return new int[] { -1, -1 };
10}

Live state

0 pairs checked

target

13

n

6

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2target=13 n=6Try every pair (i, j) with i < j. There are n(n-1)/2 = 15 of them in the worst case.
2L2target=13 i=0Fix i = 0 (value 7) and pair it with every later element.
3L4target=13 i=0 j=1 sum=18Not the target; try the next j.
4L4target=13 i=0 j=2 sum=9Not the target; try the next j.
5L4target=13 i=0 j=3 sum=22Not the target; try the next j.
6L4target=13 i=0 j=4 sum=11Not the target; try the next j.
7L4target=13 i=0 j=5 sum=16Not the target; try the next j.
8L2target=13 i=1Fix i = 1 (value 11) and pair it with every later element.
9L4target=13 i=1 j=2 sum=13This pair adds up to the target.
10L5return=[1, 2]return [1, 2] after 6 pair checks. Simple and obviously correct, but O(n²) in the worst case.
1.07.4

Identify patterns

Ask which known pattern the problem resembles: searching a sorted range (binary search), a contiguous range (prefix sums or sliding window), pairs (two pointers or hashing), nearest larger value (monotonic stack), choices with overlap (dynamic programming), connections (graph).

In the brute force, the inner loop repeatedly asks “is there a value equal to target - nums[i] later in the array?”. A question about membership is exactly what a hash set or map answers in O(1).

1.07.5

Choose structure

Choose the structure that makes the repeated question cheap. Membership by value: hash set or map. Smallest next: heap. Order and ranges: balanced tree. Position: array.

For two-sum, a HashMap<value, index> turns the inner loop into a single lookup.

1.07.6

Prove

Convince yourself the idea is right before coding. A short argument is enough: state an invariant or explain why no answer is missed. For the hash map version: when we reach index i, the map holds every earlier value, so if a partner exists earlier we will find it; if the partner comes later, it will find i when it is processed.

Proving catches ideas that work on the examples but fail in general.

1.07.7

Analyze

State time and space complexity and check them against the constraints. Hash map two-sum: one pass, O(1) expected per lookup and insert, so O(n) time and O(n) space. For n = 10⁵ this is easily fast enough, while the O(n²) baseline needs about 5 × 10⁹ pair checks.

Mention the assumption: hashing is O(1) on average; adversarial keys can degrade it.

1.07.8

Implement

Write the code from the design, keeping names meaningful and each step small. Implement the contract exactly, including what to return when there is no answer.

The animation shows the optimised solution: each iteration computes the partner value, looks it up, and stores the current value.

Java
1static int[] twoSum(int[] nums, int target) {
2 Map<Integer, Integer> seen = new HashMap<>(); // value -> index
3 for (int i = 0; i < nums.length; i++) {
4 Integer j = seen.get(target - nums[i]);
5 if (j != null) return new int[] { j, i };
6 seen.put(nums[i], i);
7 }
8 return new int[] { -1, -1 };
9}

Optimised: remember what you have seen

Interactive · edit the input

Your input

Try

int[] arr · length 6

7
11
2
15
4
9
012345
Being comparedMatch / answerStored in the map

seen (value:index)

empty
Setup

A hash map answers “have I seen this value?” in O(1) on average, so one pass is enough.

Step 1 / 7
Java
1public static int[] twoSum(int[] nums, int target) {
2 Map<Integer, Integer> seen = new HashMap<>(); // value -> index
3 for (int i = 0; i < nums.length; i++) {
4 int need = target - nums[i];
5 if (seen.containsKey(need)) {
6 return new int[] { seen.get(need), i };
7 }
8 seen.put(nums[i], i);
9 }
10 return new int[] { -1, -1 };
11}

Live state

0 lookups

target

13

No condition evaluated on this step
Execution trace (dry run table)Show 7 steps
StepLineStateWhat happens
1L2target=13A hash map answers “have I seen this value?” in O(1) on average, so one pass is enough.
2L5i=0 nums[i]=7 need=613 - 7 = 6, which has not appeared yet.
3L8i=0 size=1Store 7 → 0 for future lookups.
4L5i=1 nums[i]=11 need=213 - 11 = 2, which has not appeared yet.
5L8i=1 size=2Store 11 → 1 for future lookups.
6L5i=2 nums[i]=2 need=1111 is in the map: it was stored at index 1, so that value plus 2 makes 13.
7L6return=[1, 2]return [1, 2] after 3 lookups: O(n) time and O(n) extra memory. Brute force needed O(1) memory but O(n²) time.
1.07.9

Test

Run your hand-made examples, then compare the fast solution with the brute force on thousands of small random inputs. Mismatches show real bugs, and the failing input is already small. Use a fixed random seed so a failure is reproducible.

Include edge cases: empty, one element, duplicates, negatives and large values.

Java
1Random rnd = new Random(1);
2for (int t = 0; t < 10_000; t++) {
3 int[] a = rnd.ints(1 + rnd.nextInt(8), -5, 6).toArray();
4 int target = rnd.nextInt(11) - 5;
5 boolean fast = twoSum(a, target)[0] != -1;
6 boolean slow = false;
7 for (int i = 0; i < a.length; i++) for (int j = i + 1; j < a.length; j++) if (a[i] + a[j] == target) slow = true;
8 if (fast != slow) throw new AssertionError(Arrays.toString(a) + " target " + target);
9}
1.07.10

Optimize

Optimise only after the solution is correct and you know where the time goes. First improve the algorithm (better complexity), then the constants (primitive arrays instead of boxed collections, avoiding repeated allocation).

Measure with realistic input. Optimising code that is not the bottleneck wastes effort and makes it harder to read.

Practice ladder

Now try it yourself

Easy

  • Apply the ten steps to “find the maximum of an array” and write one example per step
  • Write a brute-force solution for “does the array contain a duplicate?”

Medium

  • Apply the workflow to “longest substring without repeating characters”
  • Write a random tester that compares two implementations

Advanced

  • Take a problem you solved earlier, identify the pattern and replace its brute force with the optimal solution, proving the new idea in two sentences

Key takeaways

  • Follow the workflow: understand, examples, brute force, pattern, structure, prove, analyse, implement, test, optimise.
  • A brute-force baseline is both a first answer and a test oracle.
  • Make a repeated question cheap by choosing the right structure.