Knapsack Problems I
The knapsack problem is a problem in combinatorial optimization: Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit and the total value is as large as possible.
416. Partition Equal Subset Sum
We can quickly get the result if the sum is an odd number. If the sum is an even number s
, it means we need to find some items in the slice which can get a sum to s / 2
. Now the problem becomes a classical 0-1 knapsack problem.
a. Classical 0-1 knapsack solution
Denote dp[i][j] as if we can construct a sum j
from the first i
items. So the state transition function is dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]
. The value is determined by:
- If we don’t use the current item, we need to check if we can construct the target
j
by the firsti - 1
items:dp[i-1][j]
- If we use the current item, we need to check if we can construct the target
j - nums[i]
by the firsti - 1
items:dp[i - 1][j - nums[i]]
1 | func canPartition(nums []int) bool { |
b. Rolling array solution
Based on the state transition function above, we can simplifiy it by using a 1D array. dp[i] = dp[i] || dp[i - nums[i]]
. Note since this time in the 1D array, the left part has side effect to the right side, so we need to iterate the array from right to left.
1 | func canPartition(nums []int) bool { |
Note: the two solutions above are using bool
value as dp array value type, we can also use int
to store the sum we can get. So the state transition function will be dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
. At the end, we just need to veryfy dp[target] == target
.
1049. Last Stone Weight II
a. Dynamic programming
To get the minimum result, we need to try our best to split the stones into two similar weight subsets. Let’s denote the sum
as the total weight of all stones, so we need to find target = sum/2
to get the minimum sum - 2 * target
1 | func lastStoneWeightII(stones []int) int { |
We can use 1D rolling array
1 | func lastStoneWeightII(stones []int) int { |
b. Brute-force Set
We can get all possible combinations of the sum and find the minimum absolute value.
1 | func lastStoneWeightII(stones []int) int { |
474. Ones and Zeroes
Each items have two properties (1 amount and 0 amount) and we need to get the maximum sum of a subset based on the two dememsion restrictions (total 1 amount n and total 0 amount m). It can be considered as a classical two dememsion 0-1 knapsack problem. So the state transition function is dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1)
(Note, ideally we need 3D array to solve this problem, but based on the state transition function, we can reduce to a 2D rolling array with reverse for-loop).
1 | func findMaxForm(strs []string, m int, n int) int { |
322. Coin Change
This is a classical full knapsack problem. The state transition function is dp[i] = min(dp[i], dp[i - coins[i]] + 1)
. Since we need to get the minimal number, so the initial value needs to be an integer which is out of the scope (except dp[0] which is 0). We can either use math.MaxInt32
or amount + 1
a. Traverse knapsack volume first.
1 | func coinChange(coins []int, amount int) int { |
b. Traverse items first.
1 | func coinChange(coins []int, amount int) int { |
518. Coin Change 2
This is is also a full knapsack problem. The difference between this and the above is that we need to get the amount of combinations. So the state transition function is dp[i] += dp[i - coins[j]]
. Since here each coin change solution is a combination problem instead of permutation problem, we can only iterate the coins first. If we iterate the knapsack space first, we will get the duplicated result like [[coins[0], coins[1]], [coins[1], coins[0]]].
1 | func change(amount int, coins []int) int { |
Based on the problems above, we can get a knapsack problem solution template
0-1 knapsack template
1 | dp := make([]int, amount + 1) |
full knapsack template to get the combination of items
1 | dp := make([]int, amount + 1) |
full knapsack template to get the permutation of items
1 | dp := make([]int, amount + 1) |