Dynamic Programming Strategies Flashcards
Largest Sum Subarray
Given an array, find the contiguous subarray with the largest sum.
Linear runtime, Constant memory
The basic idea of Kadane’s algorithm is to scan the entire array and at each position find the maximum sum of the subarray ending there. This is achieved by keeping a current maximum for the current array index and a global maximum. The algorithm is as follows:
current_max = A[0]
global_max = A[0]
for i = 1 -> size of A
if current_max is less than 0
then current_max = A[i]
otherwise
current_max = current_max + A[i]
if global_max is less than current_max
then global_max = current_max
MaxSum Subsequence - Nonadjacent Elements
Find an efficient algorithm to find the maximum sum of a subsequence in an array so that no consecutive elements are part of this subsequence. Consider the following examples; the max sums of a subsequence with no consecutive elements in the below examples are 25 and 9 respectively.
Linear runtime, linear memory.
Iterate over the entire input array, and in every iteration pick the maximum of these two values:
- Max Sum of the last iteration
- Max Sum of second last iteration + current iteration index.
This solution can be improved further by reducing the space usage. We don’t need to keep an array of previous maximum sums, only storing the last two sums would suffice. Also, the algorithm can be modified to track all the indices of this subarray with some additional data structures.
Game Scoring: Find the Number of Ways a Player can Score ‘n’ Runs
Imagine a game (like baseball) where a player can score 1,2, or 4 runs. Given a score, “n”, find the total number of ways score “n” can be reached.
We’ll use the dynamic programming approach to build the solution bottom-up. We’ll store the results in a fixed size array. Scoring options are 1, 2, and 4. The maximum score is 4, so we need to save the last four ways to calculate the number of ways for a given ‘n’. On each iteration, the result will be the sum of values present at the 3rd, 2nd, and 0th index of the results array. This is because the result at ‘n’ is the sum of values at n-1, n-2 and n-4. We’ll slide the results towards the left and save the current result at the last index.
Levenshtein Distance
Given two strings, compute the Levenshtein distance between them, meaning the minimum number of edits required to convert one string to the other.
For example, the Levenshtein distance between “kitten” and “sitting” is 3.
The minimum steps required to transform the former into the latter are:
- kitten → sitten (substitution of “s” for “k”)
- sitten → sittin (substitution of “i” for “e”)
- sittin → sitting (insertion of “g” at the end)
Quadratic Runtime, Linear Memory
The iterative algorithm with two rows for strings s1 and s2 is as follows:
if s1 is equal to s2, return 0
Set m as length of s1
Set n as length of s2
if s1 is empty, return n
if s2 is empty, return m
create two arrays of integer distances d1[] and d2[] of length n+1
initialize d1 (previous row of distances) from 0..n
for each i from 0..m
calculate d2 (current row of distances) from the previous row d1 as follows: d2[0] = i + 1 for each j from 0..n
if s1[i] is equal to s2[j], initialize cost to 0, otherwise initialize cost to 1
d2[j+1] = minimum( d2[j] +1, d1[j+1] + 1, d1[j] + cost )
copy d2 (current row) to d1 (previous row) for next iteration
return d2[n]
0/1 Knapsack Problem
Given two integer arrays to represent weights and profits of ‘N’ items, we need to find a subset of these items which will give us maximum profit such that their cumulative weight is not more than a given number ‘C’. Each item can only be selected once, which means either we put an item in the knapsack or we skip it.
Let’s take the example of Merry, who wants to carry some fruits in the knapsack to get maximum profit. Here are the weights and profits of the fruits:
- *Items:** { Apple, Orange, Banana, Melon }
- *Weights:** { 2, 3, 1, 4 }
- *Profits:** { 4, 5, 3, 7 }
- *Knapsack capacity:** 5
Let’s try to put different combinations of fruits in the knapsack, such that their total weight is not more than 5:
Apple + Orange (total weight 5) => 9 profit
Apple + Banana (total weight 3) => 7 profit
Orange + Banana (total weight 4) => 8 profit
Banana + Melon (total weight 5) => 10 profit
This shows that Banana + Melon is the best combination, as it gives us the maximum profit and the total weight does not exceed the capacity.
Dynamic Programming - 0/1 Knapsack-like - bottom-up DP solution. time and space complexity of O(N*C), where ‘N’ represents total items and ‘C’ is the maximum capacity.
Let’s try to populate our dp[][]
array working in a bottom-up fashion. Essentially, we want to find the maximum profit for every sub-array and for every possible capacity. This means, dp[i][c]
will represent the maximum knapsack profit for capacity c
calculated from the first i
items.
So, for each item at index i
(0 <= i < items.length) and capacity c
(0 <= c <= capacity), we have two options:
- Exclude the item at index
i
. In this case, we will take whatever profit we get from the sub-array excluding this item =>dp[i-1][c]
- Include the item at index
i
; if its weight is not more than the capacity. In this case, we include its profit plus whatever profit we get from the remaining capacity and from remaining items =>profit[i] + dp[i-1][c-weight[i]]
Finally, our optimal solution will be maximum of the above two values:
dp[i][c] = max (dp[i-1][c], profit[i] + dp[i-1][c-weight[i]])
Equal Subset Sum Partition
Given a set of positive numbers, find if we can partition it into two subsets such that the sum of elements in both the subsets is equal.
Example 1
Input: {1, 2, 3, 4}
Output: True
Explanation: The given set can be partitioned into two subsets with equal sum: {1, 4} & {2, 3}
Example 2
Input: {2, 3, 4, 6}
Output: False
Explanation: The given set cannot be partitioned into two subsets with equal sum.
Dynamic Programming - 0/1 Knapsack-like
Let’s try to populate our dp[][]
array from the above solution, working in a bottom-up fashion. Essentially, we want to find if we can make all possible sums with every subset. This means, dp[i][s]
will be ‘true’ if we can make sum ‘s’ from the first ‘i’ numbers.
So, for each number at index ‘i’ (0 <= i < num.length) and sum ‘s’ (0 <= s <= S/2), we have two options:
- Exclude the number. In this case, we will see if we can get ‘s’ from the subset excluding this number
dp[i-1][s]
- Include the number if its value is not more than ‘s’. In this case, we will see if we can find a subset to get the remaining sum
dp[i-1][s-num[i]]
If either of the two above scenarios is true, we can find a subset of numbers with a sum equal to ‘s’.
The above solution has time and space complexity of O(N*S), where ‘N’ represents total numbers and ‘S’ is the total sum of all the numbers.
Subset Sum
Given a set of positive numbers, determine if there exists a subset whose sum is equal to a given number ‘S’.
Example 1
Input: {1, 2, 3, 7}, S=6
Output: True
The given set has a subset whose sum is ‘6’: {1, 2, 3}
Example 2
Input: {1, 2, 7, 1, 5}, S=10
Output: True
The given set has a subset whose sum is ‘10’: {1, 2, 7}
Example 3
Input: {1, 3, 4, 8}, S=6
Output: False
The given set does not have any subset whose sum is equal to ‘6’.
Dynamic Programming - 0/1 Knapsack-like. The solution has time and space complexity of O(N*S), where ‘N’ represents total numbers and ‘S’ is the required sum.
We’ll try to find if we can make all possible sums with every subset to populate the array dp[TotalNumbers][S+1]
.
For every possible sum ‘s’ (where 0 <= s <= S), we have two options:
- Exclude the number. In this case, we will see if we can get the sum ‘s’ from the subset excluding this number =>
dp[index-1][s]
- Include the number if its value is not more than ‘s’. In this case, we will see if we can find a subset to get the remaining sum =>
dp[index-1][s-num[index]]
If either of the above two scenarios returns true, we can find a subset with a sum equal to ‘s’.
Minimum Subset Sum Difference
Given a set of positive numbers, partition the set into two subsets with a minimum difference between their subset sums.
Example 1
Input: {1, 2, 3, 9}
Output: 3
Explanation: We can partition the given set into two subsets where minimum absolute difference between the sum of numbers is ‘3’. Following are the two subsets: {1, 2, 3} & {9}.
Example 2
Input: {1, 2, 7, 1, 5}
Output: 0
Explanation: We can partition the given set into two subsets where minimum absolute difference between the sum of number is ‘0’. Following are the two subsets: {1, 2, 5} & {7, 1}.
Example 3
Input: {1, 3, 100, 4}
Output: 92
Explanation: We can partition the given set into two subsets where minimum absolute difference between the sum of numbers is ‘92’. Here are the two subsets: {1, 3, 4} & {100}.
Dynamic Programming - 0/1 Knapsack-like. time and space complexity of O(N*S), where ‘N’ represents total numbers and ‘S’ is the total sum of all the numbers.
Let’s assume ‘S’ represents the total sum of all the numbers. So what we are trying to achieve in this problem is to find a subset whose sum is as close to ‘S/2’ as possible, because if we can partition the given set into two subsets of an equal sum, we get the minimum difference i.e. zero. This transforms our problem to Subset Sum, where we try to find a subset whose sum is equal to a given number– ‘S/2’ in our case. If we can’t find such a subset, then we will take the subset which has the sum closest to ‘S/2’. This is easily possible, as we will be calculating all possible sums with every subset.
Essentially, we need to calculate all the possible sums up to ‘S/2’ for all numbers. So how do we populate the array db[TotalNumbers][S/2+1]
in the bottom-up fashion?
For every possible sum ‘s’ (where 0 <= s <= S/2), we have two options:
- Exclude the number. In this case, we will see if we can get the sum ‘s’ from the subset excluding this number =>
dp[index-1][s]
- Include the number if its value is not more than ‘s’. In this case, we will see if we can find a subset to get the remaining sum =>
dp[index-1][s-num[index]]
If either of the two above scenarios is true, we can find a subset with a sum equal to ‘s’. We should dig into this before we can learn how to find the closest subset.
Count of Subset Sum
Given a set of positive numbers, find the total number of subsets whose sum is equal to a given number ‘S’.
Example 1
Input: {1, 1, 2, 3}, S=4
Output: 3
The given set has ‘3’ subsets whose sum is ‘4’: {1, 1, 2}, {1, 3}, {1, 3} Note that we have two similar sets {1, 3}, because we have two ‘1’ in our input.
Example 2
Input: {1, 2, 7, 1, 5}, S=9
Output: 3
The given set has ‘3’ subsets whose sum is ‘9’: {2, 7}, {1, 7, 1}, {1, 2, 1, 5}
Dynamic Programming - 0/1 Knapsack-like. Time and space complexity of O(N*S), where ‘N’ represents total numbers and ‘S’ is the desired sum.
We will try to find if we can make all possible sums with every subset to populate the array db[TotalNumbers][S+1]
.
So, at every step we have two options:
- Exclude the number. Count all the subsets without the given number up to the given sum =>
dp[index-1][sum]
- Include the number if its value is not more than the ‘sum’. In this case, we will count all the subsets to get the remaining sum =>
dp[index-1][sum-num[index]]
To find the total sets, we will add both of the above two values:
dp[index][sum] = dp[index-1][sum] + dp[index-1][sum-num[index]])
Target Sum
Given a set of positive numbers (non zero) and a target sum ‘S’. Each number should be assigned either a ‘+’ or ‘-’ sign. We need to find out total ways to assign symbols to make the sum of numbers equal to target ‘S’.
Example 1
Input: {1, 1, 2, 3}, S=1
Output: 3
Explanation: The given set has ‘3’ ways to make a sum of ‘1’: {+1-1-2+3} & {-1+1-2+3} & {+1+1+2-3}
Example 2
Input: {1, 2, 7, 1}, S=9
Output: 2
Explanation: The given set has ‘2’ ways to make a sum of ‘9’: {+1+2+7-1} & {-1+2+7+1}
Dynamic Programming - 0/1 Knapsack-like. Time and space complexity of O(N*S), where ‘N’ represents total numbers and ‘S’ is the desired sum. We can further improve the solution to use only O(S) space.
We are asked to find two subsets of the given numbers whose difference is equal to the given target ‘S’. Take the first example above. As we saw, one solution is {+1-1-2+3}. So, the two subsets we are asked to find are {1, 3} & {1, 2} because,(1 + 3) - (1 + 2) = 1
Now, let’s say ‘Sum(s1)’ denotes the total sum of set ‘s1’, and ‘Sum(s2)’ denotes the total sum of set ‘s2’. So the required equation is:Sum(s1) - Sum(s2) = S
This equation can be reduced to the subset sum problem. Let’s assume that Sum(num)
denotes the total sum of all the numbers, therefore:Sum(s1) + Sum(s2) = Sum(num)
Let’s add the above tow equations:=> Sum(s1) - Sum(s2) + Sum(s1) + Sum(s2) = S + Sum(num)
=> 2 * Sum(s1) = S + Sum(num)
=> Sum(s1) = (S + Sum(num)) / 2
This essentially converts our problem to: “Find count of subsets of the given numbers whose sum is equal to”,=> (S + Sum(num)) / 2
Here is the code for the space-optimized solution, using only a single array:
Unbounded Knapsack
Given the weights and profits of ‘N’ items, we are asked to put these items in a knapsack which has a capacity ‘C’. The goal is to get the maximum profit from the items in the knapsack. The only difference between the 0/1 Knapsack problem and this problem is that we are allowed to use an unlimited quantity of an item.
Let’s take the example of Merry, who wants to carry some fruits in the knapsack to get maximum profit. Here are the weights and profits of the fruits:
- *Items:** { Apple, Orange, Melon }
- *Weights:** { 1, 2, 3 }
- *Profits:** { 15, 20, 50 }
- *Knapsack capacity:** 5
Let’s try to put different combinations of fruits in the knapsack, such that their total weight is not more than 5.
5 Apples (total weight 5) =\> 75 profit 1 Apple + 2 Oranges (total weight 5) =\> 55 profit 2 Apples + 1 Melon (total weight 5) =\> 80 profit 1 Orange + 1 Melon (total weight 5) =\> 70 profit
This shows that 2 apples + 1 melon is the best combination, as it gives us the maximum profit and the total weight does not exceed the capacity.
Dynamic Programming - Unbounded Knapsack-like. time and space complexity of O(N*C), where ‘N’ represents total items and ‘C’ is the maximum capacity.
Let’s try to populate our dp[][]
array from the above solution, working in a bottom-up fashion. Essentially, what we want to achieve is: “Find the maximum profit for every sub-array and for every possible capacity”.
So for every possible capacity ‘c’ (0 <= c <= capacity), we have two options:
Exclude the item. In this case, we will take whatever profit we get from the sub-array excluding this item:dp[index-1][c]
Include the item if its weight is not more than the ‘c’. In this case, we include its profit plus whatever profit we get from the remaining capacity: profit[index] + dp[index][c-weight[index]]
Finally, we have to take the maximum of the above two values:dp[index][c] = max (dp[index-1][c], profit[index] + dp[index][c-weight[index]])
Rod Cutting
Given a rod of length ‘n’, we are asked to cut the rod and sell the pieces in a way that will maximize the profit. We are also given the price of every piece of length ‘i’ where ‘1 <= i <= n’.
Example
- *Lengths:** [1, 2, 3, 4, 5]
- *Prices:** [2, 6, 7, 10, 13]
- *Rod Length:** 5
Let’s try different combinations of cutting the rod:
Five pieces of length 1 => 10 price
Two pieces of length 2 and one piece of length 1 => 14 price
One piece of length 3 and two pieces of length 1 => 11 price
One piece of length 3 and one piece of length 2 => 13 price
One piece of length 4 and one piece of length 1 => 12 price
One piece of length 5 => 13 price
This shows that we get the maximum price (14) by cutting the rod into two pieces of length ‘2’ and one piece of length ‘1’.
Dynamic Programming - Unbounded Knapsack-like. time and space complexity of O(N*C), where ‘N’ represents total items and ‘C’ is the maximum capacity.
Let’s try to populate our dp[][]
array in a bottom-up fashion. Essentially, what we want to achieve is: “Find the maximum sales price for every rod length and for every possible sales price”.
So for every possible rod length ‘len’ (0<= len <= n), we have two options:
- Exclude the piece. In this case, we will take whatever price we get from the rod length excluding this piece =>
dp[index-1][len]
- Include the piece if its length is not more than ‘len’. In this case, we include its price plus whatever price we get from the remaining rod length =>
prices[index] + dp[index][len-lengths[index]]
Finally, we have to take the maximum of the above two values:dp[index][len] = max (dp[index-1][len], prices[index] + dp[index][len-lengths[index]])
Coin Change
Given an infinite supply of ‘n’ coin denominations and a total money amount, we are asked to find the total number of distinct ways to make up that amount.
Example
Denominations: {1,2,3}
Total amount: 5
Output: 5
Explanation: There are five ways to make the change for ‘5’, here are those ways:
{1,1,1,1,1}
{1,1,1,2}
{1,2,2}
{1,1,3}
{2,3}
Dynamic Programming - Unbounded Knapsack-like. Time and space complexity of O(C*T), where ‘C’ represents total coin denominations and ‘T’ is the total amount that we want to make change.
We will try to find if we can make all possible sums, with every combination of coins, to populate the array dp[TotalDenominations][Total+1]
.
So for every possible total ‘t’ (0<= t <= Total) and for every possible coin index (0 <= index < denominations.length), we have two options:
- Exclude the coin. Count all the coin combinations without the given coin up to the total ‘t’ =>
dp[index-1][t]
- Include the coin if its value is not more than ‘t’. In this case, we will count all the coin combinations to get the remaining total:
dp[index][t-denominations[index]]
Finally, to find the total combinations, we will add both the above two values:
` dp[index][t] = dp[index-1][t] + dp[index][t-denominations[index]`
Minimum Coin Change
Given an infinite supply of ‘n’ coin denominations and a total money amount, we are asked to find the minimum number of coins needed to make up that amount.
Example 1
Denominations: {1,2,3}
Total amount: 5
Output: 2
Explanation: We need minimum of two coins {2,3} to make a total of ‘5’
Example 2
Denominations: {1,2,3}
Total amount: 11
Output: 4
Explanation: We need minimum four coins {2,3,3,3} to make a total of ‘11’
Dynamic Programming - Unbounded Knapsack-like. Time and space complexity of O(C*T), where ‘C’ represents total coin denominations and ‘T’ is the total amount that we want to make change.
Let’s try to populate our array dp[TotalDenominations][Total+1]
for every possible total with a minimum number of coins needed.
So for every possible total ‘t’ (0<= t <= Total) and for every possible coin index (0 <= index < denominations.length), we have two options:
- Exclude the coin: In this case, we will take the minimum coin count from the previous set =>
dp[index-1][t]
- Include the coin if its value is not more than ‘t’: In this case, we will take the minimum count needed to get the remaining total, plus include ‘1’ for the current coin =>
dp[index][t-denominations[index]] + 1
Finally, we will take the minimum of the above two values for our solution:
` dp[index][t] = min(dp[index-1][t], dp[index][t-denominations[index] + 1)`
Maximum Ribbon Cut
We are given a ribbon of length ‘n’ and a set of possible ribbon lengths. Now we need to cut the ribbon into the maximum number of pieces that comply with the above-mentioned possible lengths. Write a method that will return the count of pieces.
Example 1
n: 5
Ribbon Lengths: {2,3,5}
Output: 2
Explanation: Ribbon pieces will be {2,3}.
Example 2
n: 7
Ribbon Lengths: {2,3}
Output: 3
Explanation: Ribbon pieces will be {2,2,3}.
Example 3
n: 13
Ribbon Lengths: {3,5,7}
Output: 3
Explanation: Ribbon pieces will be {3,3,7}.
Dynamic Programming - Unbounded Knapsack-like. Time and space complexity of O(L*N), where ‘L’ represents total ribbon lengths and ‘N’ is the total length that we want to cut.
Since this problem is quite similar to Minimum Coin Change, let’s jump on to the bottom-up dynamic programming solution.
Let’s try to populate our array dp[ribbonLength][total+1]
for every possible ribbon length with a maximum number of pieces.
So for every possible length ‘len’ (0<= len <= total) and for every possible ribbon length index (0 <= index < ribbonLengths.length), we have two options:
-
Exclude the ribbon length: In this case, we will take the maximum pieces count from the previous set =>
dp[index-1][len]
-
Include the ribbon length if its value is not more than ‘len’: In this case, we will take the maximum pieces needed to get the remaining total, plus include ‘1’ for the current ribbon length =>
1 + dp[index][len-ribbonLengths[index]]
Finally, we will take the maximum of the above two values for our solution:
dp[index][t] = max(dp[index-1][len], 1 + dp[index][len-denominations[index])