Cheatsheet
Algorithms: Design Strategies
Backtracking, greedy algorithms and dynamic programming on one page: when each fits, and the Python that shows its shape.
ravnary.com/cheatsheets/algorithms-design-strategies · Checked against Python 3.14.
Scope
Backtracking, greedy algorithms, dynamic programming and its four classic tables; divide and conquer is on the Efficient Sorting sheet.The rule
Backtracking tries every choice and undoes it, greedy commits to one, and dynamic programming stores each subproblem's answer so it is solved once.
Choosing
8 choicesWhen Each item is taken whole or not, under a capacity, to make a total as large as possible
, chooseWhen You count routes across a grid that only move right or down
, chooseWhen Two sequences are compared by what they share in order, or the edits between them
, chooseWhen One sequence, and you want the most items you can keep in increasing order
, chooseWhen Every valid candidate must be listed, not only the best one or how many there are
, chooseWhen One choice at each step can be proved safe by an exchange argument
, chooseWhen The same smaller problem recurs, the answer is a best value or a count, and only some states are reached
, chooseWhen The same smaller problem recurs, the answer is a best value or a count, and every state is needed
, choose
Exploring or committing
2 entriesBoth build a solution one choice at a time: backtracking tries each choice and undoes it, and greedy keeps one.
Choose, explore, then undo the choice before the next; listing every subset costs O(n · 2ⁿ).
Use when
Every arrangement that satisfies the rules must be listed, not only the best one or a count.
Snippet
py def explore(i): if i == len(items): found.append(list(path)) return path.append(items[i]) explore(i + 1) path.pop() explore(i + 1)Take the choice that looks best now and never undo it; usually a sort then one scan.
Use when
One choice at each step can be proved safe by an exchange argument, such as earliest finish first.
Snippet
py def most_meetings(meetings): count, free_from = 0, float("-inf") for start, end in sorted(meetings, key=lambda m: m[1]): if start >= free_from: count += 1 free_from = end return count
Dynamic programming
2 entriesBoth store each subproblem's answer so it is solved once, and differ in the order the answers are filled.
Recurse from the whole problem and cache each state's answer; cost is states times work per state.
Use when
The same smaller problem recurs, and only some states are reached from the start.
Snippet
py from functools import cache def best_total(takings): @cache def best(i): if i >= len(takings): return 0 return max(best(i + 1), takings[i] + best(i + 2)) return best(0)Fill a table from the base cases up, with no recursion; the same cost as memoisation.
Use when
Every state is needed, the input is deep enough to overflow the call stack, or space must shrink.
Snippet
py def best_total(takings): best = [0] * (len(takings) + 2) for i in range(len(takings) - 1, -1, -1): best[i] = max(best[i + 1], takings[i] + best[i + 2]) return best[0]
Classic tables
4 entriesFour dynamic programmes that recur in problems, each told apart by what indexes its table.
Skip or take each item once, keeping the best value for every capacity: O(n × W) in every case.
Use when
Each item is taken whole or not at all, under a weight or budget limit, to make a total as large as possible.
Snippet
py def knapsack(weights, values, capacity): best = [0] * (capacity + 1) for weight, value in zip(weights, values): for c in range(capacity, weight - 1, -1): best[c] = max(best[c], best[c - weight] + value) return best[capacity]Each cell's count is the count from above plus the count from the left: O(rows × cols), every case.
Use when
You count or cost routes across a grid that move only right or down.
Snippet
py def count_paths(rows, cols): ways = [1] * cols for _ in range(1, rows): for c in range(1, cols): ways[c] += ways[c - 1] return ways[-1]Cell (i, j) answers for the first i and j items; a match extends the diagonal: O(m × n), every case.
Use when
Two sequences are compared by what they share in order, or by the edits that turn one into the other.
Snippet
py def lcs_length(a, b): table = [[0] * (len(b) + 1) for _ in range(len(a) + 1)] for i, x in enumerate(a, 1): for j, y in enumerate(b, 1): table[i][j] = (table[i - 1][j - 1] + 1 if x == y else max(table[i - 1][j], table[i][j - 1])) return table[-1][-1]Each position extends the best earlier run that ends on a smaller value: O(n²), every case.
Use when
One sequence, and you want the most items you can keep in increasing order.
Snippet
py def lis_length(items): best = [1] * len(items) for i in range(len(items)): for j in range(i): if items[j] < items[i]: best[i] = max(best[i], best[j] + 1) return max(best, default=0)
Side by side
2 tables| Strategy | Subproblems | Choices at each step | Typical cost | Example in this subject |
|---|---|---|---|---|
| Divide and conquer | Independent parts, each solved once | None: every part is solved and the answers combined | Set by the recurrence, often O(n log n) | Merge sort |
| Dynamic programming | Overlapping, each solved once and stored | Every choice, keeping the best or the total | Number of states × choices per state | Fewest coins; longest common subsequence |
| Greedy | One left after each choice | One, proved safe and never revisited | Usually the sort, O(n log n) | Meetings by earliest finish |
| Backtracking | A tree of partial candidates | Every choice, undone after it is explored | Exponential in the worst case, cut by pruning | Permutations; n queens |
| Aspect | Memoisation, top-down | Tabulation, bottom-up |
|---|---|---|
| How it reads | Like the recurrence, written as recursion | A loop whose order you must work out |
| States computed | Only those the answer reaches | Every state in the table |
| Call stack | As deep as the longest chain of states | None beyond the loop |
| Overhead per state | A function call and a memo lookup | An array read |
| Shrinking the space | Hard, since any stored state may be asked for again | Easy when each entry reads a fixed few before it |
Common mistakes
6 mistakesForgetting to undo the choice after the recursive call, so every later branch starts from too long a path.
Trusting a greedy rule because it works on the examples, when one counterexample breaks it.
Adding the value to the wrong term of the recurrence, which lets two neighbours both be taken.
Looping upwards over capacities in a one-array knapsack, which takes the same item more than once.
Sizing a sequence table m by n, which leaves no row or column for the empty prefix.
Memoising a recursion whose subproblems never repeat, which stores every call and reuses none.
Learn it
5 topicsBacktracking4 questions
PractiseGreedy Algorithms4 questions
PractiseDynamic Programming6 questions
PractiseKnapsack and Grid Dynamic Programming4 questions
PractiseSequence Dynamic Programming4 questions
Practise