One-dimensional dynamic programming
Dynamic programming means: build the answer for a long input from the answers for shorter ones. Each shorter answer is worked out once and written in a table, and later answers read the table. One-dimensional means the table is one row of cells.
When to use it
Use it when the answer at position i of one list depends only on the answers at a few earlier positions, so one row of cells is enough.
It belongs to the optimization group: Find the best answer without trying every possibility: reuse smaller answers, or take the safe choice first.
Watch it work
A pool sells a single ticket for one day and a triple ticket for three days in a row, at the given prices. The list has 1 for a day you swim and 0 for a day you do not. Return the fewest coins that pay for every swim day.
Input swim = [1, 0, 1, 1, 1, 0, 1] single = 2 triple = 5 Output 9
The method
- Make one cell for each number of days: no days cost 0
- Fill the cells left to right, one more day each time
- No swim on the new day: copy the cell before
- Swim day: the cheaper of a single and a triple ticket
- Return the last cell, which covers every day
The run, one moment at a time
- Step 1. Cell i of
costwill hold the fewest coins for the first i days. No days cost 0, so cell 0 is known. The cells with ? are worked out next.swim: 1 0 1 1 1 0 1 cost: 0 ? ? ? ? ? ? ?
- Step 2. Go left to right. Cell 1 is next: it covers the first day, which is
swim[0]. Every cell reads only cells on its left, which are already final.swim: 1 0 1 1 1 0 1 cost: 0 ? ? ? ? ? ? ?
- Step 4. Swim day. Single ticket added to cell 0: 0 + 2 = 2. Triple ticket added to cell 0: 0 + 5 = 5. The single is cheaper: cell 1 gets 2.
swim: 1 0 1 1 1 0 1 cost: 0 2 ? ? ? ? ? ?
- Step 3. No swim on the new day, so nothing more is paid. Cell 2 copies cell 1: 2.
swim: 1 0 1 1 1 0 1 cost: 0 2 2 ? ? ? ? ?
- Step 4. Swim day. Single ticket added to cell 2: 2 + 2 = 4. Triple ticket for the last three days, added to cell 0: 0 + 5 = 5. The single is cheaper: cell 3 gets 4.
swim: 1 0 1 1 1 0 1 cost: 0 2 2 4 ? ? ? ?
- Step 4. Swim day. Single ticket added to cell 3: 4 + 2 = 6. Triple ticket for the last three days, added to cell 1: 2 + 5 = 7. The single is cheaper: cell 4 gets 6.
swim: 1 0 1 1 1 0 1 cost: 0 2 2 4 6 ? ? ?
- Step 4. Swim day. Single ticket added to cell 4: 6 + 2 = 8. Triple ticket for the last three days, added to cell 2: 2 + 5 = 7. The triple is cheaper: cell 5 gets 7.
swim: 1 0 1 1 1 0 1 cost: 0 2 2 4 6 7 ? ?
- Step 3. No swim on the new day, so nothing more is paid. Cell 6 copies cell 5: 7.
swim: 1 0 1 1 1 0 1 cost: 0 2 2 4 6 7 7 ?
- Step 4. Swim day. Single ticket added to cell 6: 7 + 2 = 9. Triple ticket for the last three days, added to cell 4: 6 + 5 = 11. The single is cheaper: cell 7 gets 9.
swim: 1 0 1 1 1 0 1 cost: 0 2 2 4 6 7 7 9
- Step 5. Cell 7 covers all 7 days, so the answer is 9: a single, a triple and a single ticket, 2 + 5 + 2. Only single tickets would cost 10.
swim: 1 0 1 1 1 0 1 cost: 0 2 2 4 6 7 7 9
The code
The numbered comments are the steps of the method.
JavaScript
function solve(swim, single, triple) {
// 1. Make one cell for each number of days: no days cost 0
const cost = Array(swim.length + 1).fill(0);
// 2. Fill the cells left to right, one more day each time
for (let i = 1; i <= swim.length; i++) {
// 3. No swim on the new day: copy the cell before
if (swim[i - 1] === 0) {
cost[i] = cost[i - 1];
continue;
}
// 4. Swim day: the cheaper of a single and a triple ticket
const one = cost[i - 1] + single;
// A triple ticket pays for this day and the two days before it.
const three = cost[Math.max(i - 3, 0)] + triple;
cost[i] = Math.min(one, three);
}
// 5. Return the last cell, which covers every day
return cost[swim.length];
}Python
def solve(swim, single, triple):
# 1. Make one cell for each number of days: no days cost 0
cost = [0] * (len(swim) + 1)
# 2. Fill the cells left to right, one more day each time
for i in range(1, len(swim) + 1):
# 3. No swim on the new day: copy the cell before
if swim[i - 1] == 0:
cost[i] = cost[i - 1]
continue
# 4. Swim day: the cheaper of a single and a triple ticket
one = cost[i - 1] + single
# A triple ticket pays for this day and the two days before it.
three = cost[max(i - 3, 0)] + triple
cost[i] = min(one, three)
# 5. Return the last cell, which covers every day
return cost[len(swim)]How to spot it
- Asked: Count the ways to reach the end, or say if there is any way
- You are asked how many or whether, not which. The answer for a position comes from the answers for the positions one move before it.
- Asked: The largest or smallest total you can get along a row
- The best total up to a position is built from the best totals a few positions earlier. Each is worked out once and read later.
- Given: Taking one item changes which later items you may take
- No choice is safe on the spot, so both sides of every choice are compared. The cells keep that from repeating work.
- Limits: Up to 100 items, and each one is a yes or no choice
- Trying every mix of choices is 2 to the power 100, far too many. One cell for each position is about 100 cells.
Looks like it, but is not
- At every step one choice is safe: once taken, it never has to be taken back. Use Greedy choice. Then no table is needed. Dynamic programming is for tasks where the choice that looks best now can turn out worse later.
- The task wants every possible arrangement listed, and the input has about 10 items. Use Backtracking. A row of cells can count the arrangements or find the best one. To list them all, each must be built, one choice at a time.
- Each item is taken or left, and the taken items must fit a budget or reach an exact total. Use Knapsack dynamic programming. The position is not enough there, because what can still be taken depends on the amount used. The cells are numbered by amount.
Cost and mistakes
Time is about n steps, written O(n): each cell is filled once from a few earlier cells. Extra space is the row of cells, also O(n). If a cell reads only the last few cells, a few variables can replace the row.
Watch out
- Before any code, say in one sentence what a cell means. Here:
cost[i]is the fewest coins for the first i days. Every line of the code follows from that sentence. - The row has one cell more than the list, because cell 0 is the answer for no days at all. So
cost[i]goes withswim[i - 1], not withswim[i].
In the 150
9 problems of the 150 use it. Naming them gives their method away, so the list is folded.
Show the 9
- Climbing StairsEasy · 1-D Dynamic Programming
- Min Cost Climbing StairsEasy · 1-D Dynamic Programming
- House RobberMedium · 1-D Dynamic Programming
- House Robber IIMedium · 1-D Dynamic Programming
- Decode WaysMedium · 1-D Dynamic Programming
- Maximum Product SubarrayMedium · 1-D Dynamic Programming
- Word BreakMedium · 1-D Dynamic Programming
- Longest Increasing SubsequenceMedium · 1-D Dynamic Programming
- Counting BitsEasy · Bit Manipulation