Chapter 23: Dynamic Programming: The Core Method
23.07Intermediate5 topics17 min5 interactive animations

Reconstructing solutions

Recovering the actual solution from a dynamic programming table: parent pointers, choice tables, traceback, counting versus optimising and handling ties between several optimal solutions.

Learning goals

  • Store and follow parent pointers to rebuild a solution
  • Record the choice made at each state in a choice table
  • Trace back through a value table without extra storage
  • Tell counting problems from optimising ones and write both
  • Enumerate or choose among several optimal solutions

Prerequisites

Canonical beginner DPDP optimization basics

The table gives you the best value, but many problems ask for the solution itself: which items, which steps, which coins. A little extra bookkeeping recovers it.

23.07.1

Parent pointers

A **parent pointer** records, for every state, which previous state gave its best value. After the table is filled, start from the final state and follow the pointers back to the start; the visited states form the solution, in reverse order.

The minimum-cost-climbing animation shows it on costs 10, 15, 20, 5, 30, 8: the predecessors give the path of steps 1, 3 and 5, with total cost 28. The same bookkeeping appears in shortest paths in graphs.

Java
1// returns the steps stood on, in order, for the cheapest climb; position n is the top
2static List<Integer> cheapestSteps(int[] cost) {
3 int n = cost.length;
4 int[] dp = new int[n + 1], parent = new int[n + 1];
5 for (int i = 2; i <= n; i++) {
6 int fromOne = dp[i - 1] + cost[i - 1], fromTwo = dp[i - 2] + cost[i - 2];
7 if (fromOne <= fromTwo) { dp[i] = fromOne; parent[i] = i - 1; } // remember where the best value came from
8 else { dp[i] = fromTwo; parent[i] = i - 2; }
9 }
10 LinkedList<Integer> steps = new LinkedList<>();
11 for (int at = parent[n]; ; at = parent[at]) { // follow the pointers back from the top
12 steps.addFirst(at);
13 if (at <= 1) break;
14 }
15 return steps;
16}

Min cost climbing stairs, with traceback

Interactive · edit the input

Your input

Try

cost[i]

10i=0
15i=1
20i=2
5i=3
30i=4
8i=5

dp[i] = cheapest way to stand on i

10start
15start
?start
?start
?start
?start
Setup

You can start on step 0 or step 1, so dp[0] = cost[0] and dp[1] = cost[1]. Every later step is reached from one of the two steps below it.

Step 1 / 7
Java
1static int minCost(int[] cost) { // you may start on step 0 or 1 and pay cost[i] when you leave step i
2 int n = cost.length; int[] dp = new int[n]; // dp[i] = cheapest total to stand on step i, including cost[i]
3 dp[0] = cost[0]; dp[1] = cost[1];
4 for (int i = 2; i < n; i++) dp[i] = cost[i] + Math.min(dp[i - 1], dp[i - 2]); // best predecessor, then pay for this step
5 return Math.min(dp[n - 1], dp[n - 2]); // the top is one step past the end, reached from either of the last two
6}

Live state

steps

6

No condition evaluated on this step
Execution trace (dry run table)Show 7 steps
StepLineStateWhat happens
1L3steps=6You can start on step 0 or step 1, so dp[0] = cost[0] and dp[1] = cost[1]. Every later step is reached from one of the two steps below it.
2L4i=2 dp[i-1]=15 dp[i-2]=10 dp[i]=30dp[2] = cost[2] + min(15, 10) = 20 + 10 = 30. The cheaper predecessor is step 0 (remembered for the traceback).
3L4i=3 dp[i-1]=30 dp[i-2]=15 dp[i]=20dp[3] = cost[3] + min(30, 15) = 5 + 15 = 20. The cheaper predecessor is step 1 (remembered for the traceback).
4L4i=4 dp[i-1]=20 dp[i-2]=30 dp[i]=50dp[4] = cost[4] + min(20, 30) = 30 + 20 = 50. The cheaper predecessor is step 3 (remembered for the traceback).
5L4i=5 dp[i-1]=50 dp[i-2]=20 dp[i]=28dp[5] = cost[5] + min(50, 20) = 8 + 20 = 28. The cheaper predecessor is step 3 (remembered for the traceback).
6L5top reached from=5 cost=28The top (one step past the end) is cheapest from step 5: total 28.
7L5return=28 path=[1, 3, 5]Minimum cost 28. Following the remembered predecessors backwards gives the steps stood on: [1, 3, 5].
23.07.2

Choice tables

A **choice table** stores the decision taken in each cell (take or skip, which coin, which split) in a second table the same shape as the value table. Reading the choices from the final cell backwards reconstructs the solution. It costs extra memory but makes the traceback trivial and is the natural tool when the decision is not recoverable from the values alone.

In the knapsack animation (the “2D table” preset), a cell that differs from the cell above it means the item was taken, so the choices are read directly off the table: items #1 and #2 are taken for a value of 35.

Java
1// returns the 1-based indices of the chosen items
2static List<Integer> chosenItems(int[][] items, int capacity) {
3 int n = items.length;
4 int[][] dp = new int[n + 1][capacity + 1];
5 boolean[][] took = new boolean[n + 1][capacity + 1]; // the choice made in each cell
6 for (int i = 1; i <= n; i++) {
7 for (int w = 0; w <= capacity; w++) {
8 dp[i][w] = dp[i - 1][w];
9 if (items[i - 1][0] <= w && dp[i - 1][w - items[i - 1][0]] + items[i - 1][1] > dp[i][w]) {
10 dp[i][w] = dp[i - 1][w - items[i - 1][0]] + items[i - 1][1];
11 took[i][w] = true;
12 }
13 }
14 }
15 LinkedList<Integer> chosen = new LinkedList<>();
16 for (int i = n, w = capacity; i >= 1; i--) {
17 if (took[i][w]) { chosen.addFirst(i); w -= items[i - 1][0]; }
18 }
19 return chosen;
20}

0/1 knapsack: 2D table, and why 1D must run backwards

Interactive · edit the input

Your input

Try

grid · 5 × 6

012345
0
item \ cap
0
1
2
3
4
1
none
0
0
0
0
0
2
#1 (1:15)
3
#2 (3:20)
4
#3 (4:30)
Cell being computedCell it readsOn the tracebackWrong: item reused
Setup

dp[i][c] is the best value from the first i items with capacity c. Row 0 is all zeros. Each cell either skips item i (copy the cell above) or takes it (value + the cell above-left by the item's weight).

Step 1 / 17
Java
1static int knapsack2D(int[] w, int[] v, int cap) {
2 int n = w.length; int[][] dp = new int[n + 1][cap + 1]; // dp[i][c]: best value from the first i items with capacity c
3 for (int i = 1; i <= n; i++)
4 for (int c = 0; c <= cap; c++) {
5 dp[i][c] = dp[i - 1][c]; // skip item i
6 if (c >= w[i - 1]) dp[i][c] = Math.max(dp[i][c], dp[i - 1][c - w[i - 1]] + v[i - 1]); // or take it
7 }
8 return dp[n][cap];
9}
10static int knapsack1D(int[] w, int[] v, int cap, boolean backwards) {
11 int[] dp = new int[cap + 1];
12 for (int i = 0; i < w.length; i++) {
13 if (backwards) for (int c = cap; c >= w[i]; c--) dp[c] = Math.max(dp[c], dp[c - w[i]] + v[i]); // right to left: dp[c - w] still excludes item i
14 else for (int c = w[i]; c <= cap; c++) dp[c] = Math.max(dp[c], dp[c - w[i]] + v[i]); // left to right: item i may be taken again (a bug for 0/1)
15 }
16 return dp[cap];
17}

Live state

items

3

capacity

4

No condition evaluated on this step
Execution trace (dry run table)Show 17 steps
StepLineStateWhat happens
1L2items=3 capacity=4dp[i][c] is the best value from the first i items with capacity c. Row 0 is all zeros. Each cell either skips item i (copy the cell above) or takes it (value + the cell above-left by the item's weight).
2L6item=1 c=0 skip=0 take=does not fit dp[i][c]=0Item 1 (weight 1) does not fit in capacity 0: copy the cell above (0).
3L6item=1 c=1 skip=0 take=15 dp[i][c]=15Capacity 1: skip item 1 → 0; take it → 0 + 15 = 15. Keep 15.
4L6item=1 c=2 skip=0 take=15 dp[i][c]=15Capacity 2: skip item 1 → 0; take it → 0 + 15 = 15. Keep 15.
5L6item=1 c=3 skip=0 take=15 dp[i][c]=15Capacity 3: skip item 1 → 0; take it → 0 + 15 = 15. Keep 15.
6L6item=1 c=4 skip=0 take=15 dp[i][c]=15Capacity 4: skip item 1 → 0; take it → 0 + 15 = 15. Keep 15.
7L6item=2 c=0 skip=0 take=does not fit dp[i][c]=0Item 2 (weight 3) does not fit in capacity 0: copy the cell above (0).
8L6item=2 c=1 skip=15 take=does not fit dp[i][c]=15Item 2 (weight 3) does not fit in capacity 1: copy the cell above (15).
9L6item=2 c=2 skip=15 take=does not fit dp[i][c]=15Item 2 (weight 3) does not fit in capacity 2: copy the cell above (15).
10L6item=2 c=3 skip=15 take=20 dp[i][c]=20Capacity 3: skip item 2 → 15; take it → 0 + 20 = 20. Keep 20.
11L6item=2 c=4 skip=15 take=35 dp[i][c]=35Capacity 4: skip item 2 → 15; take it → 15 + 20 = 35. Keep 35.
12L6item=3 c=0 skip=0 take=does not fit dp[i][c]=0Item 3 (weight 4) does not fit in capacity 0: copy the cell above (0).
13L6item=3 c=1 skip=15 take=does not fit dp[i][c]=15Item 3 (weight 4) does not fit in capacity 1: copy the cell above (15).
14L6item=3 c=2 skip=15 take=does not fit dp[i][c]=15Item 3 (weight 4) does not fit in capacity 2: copy the cell above (15).
15L6item=3 c=3 skip=20 take=does not fit dp[i][c]=20Item 3 (weight 4) does not fit in capacity 3: copy the cell above (20).
16L6item=3 c=4 skip=35 take=30 dp[i][c]=35Capacity 4: skip item 3 → 35; take it → 0 + 30 = 30. Keep 35.
17L8return=35 items=[#1, #2]Best value 35. Traceback from the bottom-right cell: whenever a cell differs from the one above it, that item was taken (items [#1, #2]), and we move left by its weight.
23.07.3

Traceback

A **traceback** recovers the solution from the value table alone, by re-asking at each state which option produced the stored value. No extra table is needed; you pay a second, linear pass. For house robber, start at house n: if best[i] == best[i - 1] the house was skipped, otherwise it was robbed and you jump to house i - 2.

On the “Classic” preset (2, 7, 9, 3, 1) the traceback goes house 5 (robbed, 12 differs from 11), house 3 (robbed), house 1 (robbed), giving houses 1, 3 and 5.

Java
1// best[i] from the earlier lesson: best loot from the first i houses; returns the robbed houses (1-based)
2static List<Integer> robbedHouses(int[] value) {
3 int n = value.length;
4 int[] best = new int[n + 1];
5 for (int i = 1; i <= n; i++) best[i] = Math.max(best[i - 1], (i >= 2 ? best[i - 2] : 0) + value[i - 1]);
6 LinkedList<Integer> houses = new LinkedList<>();
7 int i = n;
8 while (i >= 1) {
9 if (best[i] == best[i - 1]) i--; // skipped: the value did not change
10 else { houses.addFirst(i); i -= 2; } // robbed: jump over the neighbour
11 }
12 return houses;
13}

House robber: take it or skip it

Interactive · edit the input

Your input

Try

house money

2h1
7h2
9h3
3h4
1h5

dp[i] = best from the first i houses

0none
2rob h1
?i=2
?i=3
?i=4
?i=5
Setup

For the first i houses, the best loot either skips house i (best of the first i−1) or robs it (its money plus the best of the first i−2, because the neighbour is off limits).

Step 1 / 6
Java
1static int rob(int[] a) { // no two adjacent houses
2 int n = a.length; int[] dp = new int[n + 1]; // dp[i] = best loot from the first i houses
3 dp[1] = a[0];
4 for (int i = 2; i <= n; i++)
5 dp[i] = Math.max(dp[i - 1], dp[i - 2] + a[i - 1]); // skip house i, or rob it and use the best before the neighbour
6 return dp[n];
7}

Live state

houses

5

No condition evaluated on this step
Execution trace (dry run table)Show 6 steps
StepLineStateWhat happens
1L3houses=5For the first i houses, the best loot either skips house i (best of the first i−1) or robs it (its money plus the best of the first i−2, because the neighbour is off limits).
2L5i=2 skip=2 rob=7 dp[i]=7House 2: skipping keeps 2; robbing gives dp[0] + 7 = 7. Best 7 (rob it).
3L5i=3 skip=7 rob=11 dp[i]=11House 3: skipping keeps 7; robbing gives dp[1] + 9 = 11. Best 11 (rob it).
4L5i=4 skip=11 rob=10 dp[i]=11House 4: skipping keeps 11; robbing gives dp[2] + 3 = 10. Best 11 (skip it).
5L5i=5 skip=11 rob=12 dp[i]=12House 5: skipping keeps 11; robbing gives dp[3] + 1 = 12. Best 12 (rob it).
6L6return=12 houses=[1, 3, 5]Maximum loot 12. Tracing the take/skip decisions backwards from house 5 gives the houses [1, 3, 5].
23.07.4

Counting vs optimizing

The same table shape answers two kinds of questions. An **optimising** DP combines options with min or max; a **counting** DP combines them with +. Coin change shows both: the fewest coins use min over the last coin, and the number of ways to make an amount uses +.

Counting has a further subtlety: **order**. If the loops run coins outside and amounts inside, each combination is counted once (the coins used are fixed in the order of the outer loop). If amounts run outside and coins inside, every ordered sequence is counted. For coins 1, 2, 5 and amount 11 there are 11 combinations and 218 ordered sequences. The animation's “Count combinations” preset reports the 11.

Java
1// number of combinations (order does not matter): coins outer, amounts inner
2static long countCombinations(int[] coins, int amount) {
3 long[] ways = new long[amount + 1];
4 ways[0] = 1;
5 for (int c : coins) for (int a = c; a <= amount; a++) ways[a] += ways[a - c];
6 return ways[amount];
7}
8
9// number of ordered sequences: amounts outer, coins inner
10static long countSequences(int[] coins, int amount) {
11 long[] ways = new long[amount + 1];
12 ways[0] = 1;
13 for (int a = 1; a <= amount; a++) for (int c : coins) if (c <= a) ways[a] += ways[a - c];
14 return ways[amount];
15}

Coin change: fewest coins, or number of ways

Interactive · edit the input

Your input

Try

dp[a] = fewest coins

0a=0
∞a=1
∞a=2
∞a=3
∞a=4
∞a=5
∞a=6

last coin used

-a=0
-a=1
-a=2
-a=3
-a=4
-a=5
-a=6
Setup

dp[a] is the fewest coins that make a. dp[0] = 0, everything else starts at ∞ (not reachable yet). For each amount try every coin c: use one c and add 1 to the best for a − c.

Step 1 / 15
Java
1static int minCoins(int[] coins, int amount) {
2 int INF = amount + 1;
3 int[] dp = new int[amount + 1]; Arrays.fill(dp, INF); dp[0] = 0; // dp[a] = fewest coins that make a
4 for (int a = 1; a <= amount; a++)
5 for (int c : coins)
6 if (c <= a && dp[a - c] + 1 < dp[a]) dp[a] = dp[a - c] + 1; // one coin c, then solve a - c
7 return dp[amount] >= INF ? -1 : dp[amount];
8}
9static long ways(int[] coins, int amount) {
10 long[] dp = new long[amount + 1]; dp[0] = 1; // one way to make 0: take nothing
11 for (int c : coins) // coins in the outer loop: each combination is counted once
12 for (int a = c; a <= amount; a++) dp[a] += dp[a - c];
13 return dp[amount];
14}

Live state

amount

6

coins

[1, 3, 4]

No condition evaluated on this step
Execution trace (dry run table)Show 15 steps
StepLineStateWhat happens
1L3amount=6 coins=[1, 3, 4]dp[a] is the fewest coins that make a. dp[0] = 0, everything else starts at ∞ (not reachable yet). For each amount try every coin c: use one c and add 1 to the best for a − c.
2L6a=1 coin=1 candidate=1 dp[a]=1Coin 1: dp[0] + 1 = 1 beats the old value: dp[1] = 1.
3L6a=2 coin=1 candidate=2 dp[a]=2Coin 1: dp[1] + 1 = 2 beats the old value: dp[2] = 2.
4L6a=3 coin=1 candidate=3 dp[a]=3Coin 1: dp[2] + 1 = 3 beats the old value: dp[3] = 3.
5L6a=3 coin=3 candidate=1 dp[a]=1Coin 3: dp[0] + 1 = 1 beats the old value: dp[3] = 1.
6L6a=4 coin=1 candidate=2 dp[a]=2Coin 1: dp[3] + 1 = 2 beats the old value: dp[4] = 2.
7L6a=4 coin=3 candidate=2 dp[a]=2Coin 3: dp[1] + 1 = 2 does not improve dp[4] = 2.
8L6a=4 coin=4 candidate=1 dp[a]=1Coin 4: dp[0] + 1 = 1 beats the old value: dp[4] = 1.
9L6a=5 coin=1 candidate=2 dp[a]=2Coin 1: dp[4] + 1 = 2 beats the old value: dp[5] = 2.
10L6a=5 coin=3 candidate=3 dp[a]=2Coin 3: dp[2] + 1 = 3 does not improve dp[5] = 2.
11L6a=5 coin=4 candidate=2 dp[a]=2Coin 4: dp[1] + 1 = 2 does not improve dp[5] = 2.
12L6a=6 coin=1 candidate=3 dp[a]=3Coin 1: dp[5] + 1 = 3 beats the old value: dp[6] = 3.
13L6a=6 coin=3 candidate=2 dp[a]=2Coin 3: dp[3] + 1 = 2 beats the old value: dp[6] = 2.
14L6a=6 coin=4 candidate=3 dp[a]=2Coin 4: dp[2] + 1 = 3 does not improve dp[6] = 2.
15L7return=2 coins=[3, 3]Fewest coins: 2. Following the last coin used from 6 down to 0 gives the coins [3, 3]. Other optimal combinations may exist; the traceback shows the one the table remembers.
23.07.5

Multiple optimal solutions

The best value is unique, but the solution achieving it often is not. Coins 1, 2, 3 and amount 4 can be made with two coins as 1 + 3 or as 2 + 2; the “Several optimal answers” preset shows the traceback choosing 1 + 3 and notes that others may exist. Which one you get depends on tie-breaking: < versus <= in the comparison, or the order the options are tried.

If any optimal solution is acceptable, take whichever the traceback finds, but make the tie-break deterministic. If the problem asks for a specific one (lexicographically smallest, fewest items among best-value solutions) fold the tie-break into the transition. To list all optimal solutions, search the table: from each state, follow every option whose value is consistent with the optimum, as in the code below, which lists every optimal multiset of coins.

Java
1// all coin multisets with the fewest coins, each listed in non-decreasing order; coins must be sorted ascending
2static List<List<Integer>> optimalCombinations(int[] coins, int amount) {
3 int[] dp = new int[amount + 1];
4 Arrays.fill(dp, Integer.MAX_VALUE);
5 dp[0] = 0;
6 for (int a = 1; a <= amount; a++)
7 for (int c : coins) if (c <= a && dp[a - c] != Integer.MAX_VALUE) dp[a] = Math.min(dp[a], dp[a - c] + 1);
8 List<List<Integer>> out = new ArrayList<>();
9 if (dp[amount] != Integer.MAX_VALUE) collect(coins, dp, amount, coins.length - 1, new ArrayDeque<>(), out);
10 return out;
11}
12
13// walk the table from 'remaining' down to 0, choosing coins in non-increasing order to avoid duplicates
14static void collect(int[] coins, int[] dp, int remaining, int maxIndex, Deque<Integer> path, List<List<Integer>> out) {
15 if (remaining == 0) { out.add(new ArrayList<>(path)); return; }
16 for (int i = maxIndex; i >= 0; i--) {
17 int c = coins[i];
18 if (c <= remaining && dp[remaining - c] == dp[remaining] - 1) { // this coin lies on some optimal solution
19 path.addFirst(c);
20 collect(coins, dp, remaining - c, i, path, out);
21 path.removeFirst();
22 }
23 }
24}

Coin change: fewest coins, or number of ways

Interactive · edit the input

Your input

Try

dp[a] = fewest coins

0a=0
∞a=1
∞a=2
∞a=3
∞a=4
∞a=5
∞a=6

last coin used

-a=0
-a=1
-a=2
-a=3
-a=4
-a=5
-a=6
Setup

dp[a] is the fewest coins that make a. dp[0] = 0, everything else starts at ∞ (not reachable yet). For each amount try every coin c: use one c and add 1 to the best for a − c.

Step 1 / 15
Java
1static int minCoins(int[] coins, int amount) {
2 int INF = amount + 1;
3 int[] dp = new int[amount + 1]; Arrays.fill(dp, INF); dp[0] = 0; // dp[a] = fewest coins that make a
4 for (int a = 1; a <= amount; a++)
5 for (int c : coins)
6 if (c <= a && dp[a - c] + 1 < dp[a]) dp[a] = dp[a - c] + 1; // one coin c, then solve a - c
7 return dp[amount] >= INF ? -1 : dp[amount];
8}
9static long ways(int[] coins, int amount) {
10 long[] dp = new long[amount + 1]; dp[0] = 1; // one way to make 0: take nothing
11 for (int c : coins) // coins in the outer loop: each combination is counted once
12 for (int a = c; a <= amount; a++) dp[a] += dp[a - c];
13 return dp[amount];
14}

Live state

amount

6

coins

[1, 3, 4]

No condition evaluated on this step
Execution trace (dry run table)Show 15 steps
StepLineStateWhat happens
1L3amount=6 coins=[1, 3, 4]dp[a] is the fewest coins that make a. dp[0] = 0, everything else starts at ∞ (not reachable yet). For each amount try every coin c: use one c and add 1 to the best for a − c.
2L6a=1 coin=1 candidate=1 dp[a]=1Coin 1: dp[0] + 1 = 1 beats the old value: dp[1] = 1.
3L6a=2 coin=1 candidate=2 dp[a]=2Coin 1: dp[1] + 1 = 2 beats the old value: dp[2] = 2.
4L6a=3 coin=1 candidate=3 dp[a]=3Coin 1: dp[2] + 1 = 3 beats the old value: dp[3] = 3.
5L6a=3 coin=3 candidate=1 dp[a]=1Coin 3: dp[0] + 1 = 1 beats the old value: dp[3] = 1.
6L6a=4 coin=1 candidate=2 dp[a]=2Coin 1: dp[3] + 1 = 2 beats the old value: dp[4] = 2.
7L6a=4 coin=3 candidate=2 dp[a]=2Coin 3: dp[1] + 1 = 2 does not improve dp[4] = 2.
8L6a=4 coin=4 candidate=1 dp[a]=1Coin 4: dp[0] + 1 = 1 beats the old value: dp[4] = 1.
9L6a=5 coin=1 candidate=2 dp[a]=2Coin 1: dp[4] + 1 = 2 beats the old value: dp[5] = 2.
10L6a=5 coin=3 candidate=3 dp[a]=2Coin 3: dp[2] + 1 = 3 does not improve dp[5] = 2.
11L6a=5 coin=4 candidate=2 dp[a]=2Coin 4: dp[1] + 1 = 2 does not improve dp[5] = 2.
12L6a=6 coin=1 candidate=3 dp[a]=3Coin 1: dp[5] + 1 = 3 beats the old value: dp[6] = 3.
13L6a=6 coin=3 candidate=2 dp[a]=2Coin 3: dp[3] + 1 = 2 beats the old value: dp[6] = 2.
14L6a=6 coin=4 candidate=3 dp[a]=2Coin 4: dp[2] + 1 = 3 does not improve dp[6] = 2.
15L7return=2 coins=[3, 3]Fewest coins: 2. Following the last coin used from 6 down to 0 gives the coins [3, 3]. Other optimal combinations may exist; the traceback shows the one the table remembers.

Practice ladder

Now try it yourself

Easy

  • Min Cost Climbing Stairs (path)
  • Climbing Stairs (count)

Medium

  • Coin Change II
  • Longest Increasing Subsequence (print one)
  • Target Sum

Advanced

  • Longest Palindromic Subsequence (print)
  • Shortest Common Supersequence

Key takeaways

  • Parent pointers record where each state's best value came from; follow them back from the goal.
  • A choice table stores the decision per cell; a traceback re-derives it from the values without storage.
  • Counting uses + and optimising uses min or max over the same state structure.
  • Loop order decides whether combinations or ordered sequences are counted.
  • Ties give several optimal solutions: fix a tie-break, or enumerate them from the table.