How to Approach a Problem

A repeatable seven-step method for problems you have never seen, worked through on a full example, plus a cheat table for picking the right pattern.

18 minLesson 5 of 5
On this page
  1. The seven steps
  2. A worked problem
  3. Which pattern fits
  4. Common mistakes
  5. Check yourself
  6. What to practise next

You open a new problem, read it once, and your mind goes blank. Or you start typing straight away, get halfway, and realise you are solving the wrong thing. Both happen to everyone.

Experienced people do not have a better memory for tricks. They have a routine that gets them from "no idea" to working code without a flash of insight. Here it is in seven steps, then one problem solved from start to finish.

The seven steps

  1. Restate the problem in your own words. Name the input, the output, and the size limits. Ask what happens in the odd cases: empty input, no answer, ties.
  2. Solve a small example by hand. Use 5 to 8 items. Watch what you do; that is a first algorithm.
  3. Write the brute force. The most obvious correct method, even if it is slow. Work out its cost.
  4. Find the waste. What does the brute force compute again and again, or check when it could not possibly matter?
  5. Pick a pattern that removes that waste. The cheat table below helps.
  6. Write the code for the better method.
  7. Test edge cases by hand or by running them: empty, one item, all the same, the answer at each end.

Steps 1 and 2 feel slow. Skip them and you pay later, debugging an answer to a question nobody asked.

A worked problem

Your phone counts your steps each day. Your goal is 8,000 a day. Given the daily counts, find your longest streak: the most days in a row on which you hit the goal.

1. Restate it

Input: a list of whole numbers steps, one per day in order, and a number goal. Output: one number, the length of the longest run of neighbouring days where every day has steps[i] >= goal.

The odd cases need decisions before any code exists. Exactly 8,000 counts as hitting the goal ("at least"). If no day hits the goal, the answer is 0. An empty list also gives 0. Suppose the app keeps up to 100,000 days of history: that is the size to plan for.

2. Solve a small example by hand

Take a week: 8,200, 9,100, 4,000, 8,500, 10,200, 9,000, 2,500. Mark each day: ✓ ✓ ✗ ✓ ✓ ✓ ✗. The runs of ticks have lengths 2 and 3, so the answer is 3.

Notice what your eyes did: you scanned once and counted ticks, starting over at each cross. Keep that in mind.

3. Write the brute force

The most literal method: try every day as the start of a streak, walk forward while the goal is met, and keep the longest walk.

Brute force
function longestStreakSlow(steps, goal) {
  let best = 0;
  for (let start = 0; start < steps.length; start++) {
    let length = 0;
    while (start + length < steps.length && steps[start + length] >= goal) {
      length++;
    }
    best = Math.max(best, length);
  }
  return best;
}
def longest_streak_slow(steps, goal):
    best = 0
    for start in range(len(steps)):
        length = 0
        while start + length < len(steps) and steps[start + length] >= goal:
            length += 1
        best = max(best, length)
    return best

It is correct, which gives you something to test against. Its cost: if every day hits the goal, the walks have lengths n, n − 1, ..., 1. That sum is about n²/2 (see Math for Algorithms). With 100,000 days that is 5 billion steps. Too slow.

4. Find the waste

Look at the walks that start inside the long streak.

steps 8200 9100 4000 8500 10200 9000 2500 ✓ ✓ ✗ ✓ ✓ ✓ ✗ start 3 walk of 3, then stop start 4 walk of 2 start 5 walk of 1
The brute force walks over the same days again from every start. Days 3, 4 and 5 are each checked several times.

Two kinds of waste show up. A walk that starts in the middle of a streak can never beat the walk that started at the beginning of it. And when one walk stops at day 6, the next walk re-checks the same days only to stop at day 6 again.

A good question to ask: "What do I know about today, given what I knew yesterday?" If today hits the goal, the streak ending today is the streak ending yesterday plus 1. If it does not, the streak ending today is 0. One number carried from day to day replaces all the walks.

5. Pick a pattern

This is the simplest pattern there is: one pass, carrying a running value. You saw it already as "best so far" in What Is an Algorithm. Here you carry two values: the current streak and the best streak. It is also the simplest case of a sliding window.

6. Write the code

Longest streak in one passUse the arrows or ← → keys
best = 0, current = 0for each day d:  if d >= goal: current = current + 1  else: current = 0  best = max(best, current)return best
  1. steps
    08200
    19100
    24000
    38500
    410200
    59000
    62500
    goal
    8000
    current
    0
    best
    0

    1No days seen yet, so no streak.

  2. steps
    08200d
    19100
    24000
    38500
    410200
    59000
    62500
    goal
    8000
    current
    1
    best
    1

    28,200 hits the goal. The streak ending today is 1 day. Best becomes 1.

  3. steps
    08200
    19100d
    24000
    38500
    410200
    59000
    62500
    goal
    8000
    current
    2
    best
    2

    39,100 hits the goal too. Yesterday's streak plus 1: current is 2, best is 2.

  4. steps
    08200
    19100
    24000d
    38500
    410200
    59000
    62500
    goal
    8000
    current
    0
    best
    2

    44,000 misses. No streak can end today, so current drops to 0. Best keeps the 2 it already saw.

  5. steps
    08200
    19100
    24000
    38500d
    410200
    59000
    62500
    goal
    8000
    current
    1
    best
    2

    58,500 hits. A new streak starts at 1. Best stays 2.

  6. steps
    08200
    19100
    24000
    38500
    410200d
    59000
    62500
    goal
    8000
    current
    2
    best
    2

    610,200 hits. Current is 2, which only ties best.

  7. steps
    08200
    19100
    24000
    38500
    410200
    59000d
    62500
    goal
    8000
    current
    3
    best
    3

    79,000 hits. Current is 3, a new best.

  8. steps
    08200
    19100
    24000
    38500
    410200
    59000
    62500d
    goal
    8000
    current
    0
    best
    3

    82,500 misses. Current resets to 0, best stays 3.

  9. steps
    08200
    19100
    24000
    38500
    410200
    59000
    62500
    answer
    3
    checks
    7
    brute force
    16

    9The answer is 3. Each day was looked at once: 7 checks. The brute force made 16 checks on the same week.

1 / 9
One pass
function longestStreak(steps, goal) {
  let best = 0;
  let current = 0; // length of the streak that ends today
  for (const day of steps) {
    current = day >= goal ? current + 1 : 0;
    best = Math.max(best, current); // update every day
  }
  return best;
}
 
longestStreak([8200, 9100, 4000, 8500, 10200, 9000, 2500], 8000); // 3
def longest_streak(steps, goal):
    best = 0
    current = 0  # length of the streak that ends today
    for day in steps:
        current = current + 1 if day >= goal else 0
        best = max(best, current)  # update every day
    return best
 
longest_streak([8200, 9100, 4000, 8500, 10200, 9000, 2500], 8000)  # 3
ApproachTimeSpaceWhy
Walk from every startO(n²)O(1)Up to n + (n − 1) + ... + 1 checks when every day hits the goal.
One passO(n)O(1)Each day is checked once. Only two numbers are kept.

7. Test the edge cases

Run each by hand through the one-pass code:

  • []: the loop never runs, returns 0. Correct.
  • [3000, 5000]: no day hits, current stays 0, returns 0. Correct.
  • [8000]: exactly the goal counts, returns 1. Correct.
  • [9000, 9000, 9000]: every day hits, returns 3. Correct.
  • [2000, 9000, 9000]: the best streak is at the very end, returns 2. Correct. This case catches the most bugs (see below).

Also run both versions on many random small lists and check they agree. That finds bugs that staring never will.

Which pattern fits

Problems give off signals. Treat this table as a starting point, not a rule.

If the problem…Try
asks "have I seen this before?", or needs a partner value, or counts thingsHash map or set
has a sorted array and asks about pairsTwo pointers
asks for the longest or shortest stretch of neighbouring itemsSliding window
asks many questions about sums of rangesPrefix sums
has sorted data, or asks for the smallest value that worksBinary search, on the answer
needs the next bigger item, or matching bracketsStack, monotonic stack
has nested data or a treeRecursion, tree traversals
asks for the fewest moves in a grid or networkBreadth-first search
asks for the cheapest route with costs on the linksDijkstra
has tasks where some must come before othersTopological sort
keeps merging groups, or asks "are these connected?"Union-find
wants every combination, order or arrangementBacktracking
wants the k largest, or keeps taking the smallestHeap
is about meetings or time ranges that overlapIntervals
counts ways, or wants a best total built from smaller choicesDynamic programming
seems solvable by always taking the best-looking optionGreedy, but prove it first
searches words by their beginningTrie

The size limits are a signal too. At a few hundred million simple steps per second, they tell you roughly what cost you can afford:

n up to aboutAffordable cost
10O(n!): every order
20O(2ⁿ): every subset
a few thousandO(n²): every pair
1,000,000O(n log n)
100,000,000O(n), and only simple work per item
beyond thatO(log n) or O(1): you cannot even read every item

Common mistakes

Check yourself

Quick check

You have read a new problem once. What should you do first?

The limits say a list can hold up to 100,000 numbers. Your brute force is O(n²). What does that tell you?

With goal 8,000, what does the one-pass longestStreak return for [9000, 7000, 8000, 8100]?

What to practise next

This finishes the foundations. Next, Arrays starts the everyday tools, and the patterns in the table above come one lesson at a time. Use the seven steps on every problem you practise, even the easy ones, until they are a habit.