0. Origin
0-1 knapsack: max min problem
-
Concept: there are N items in total. The weight of item I (I starts from 1) is w[i], and the value is v[i]. When the total weight does not exceed the upper limit W of the backpack, what is the maximum value that can be loaded into the backpack?
-
Idea: define a two-dimensional array dp to store the maximum value, where dp[i][j] represents the maximum value that can be achieved when the volume of the first I items does not exceed j. Let the volume of the i-th item be w and the value be v. according to whether the i-th item is added to the backpack, it can be discussed in two cases:
- The i-th item is not added to the backpack. The maximum value of the first I items with a total volume of no more than j is the maximum value of the first i-1 items with a total volume of no more than J, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
- Add the i-th item to the backpack, d p [ i ] [ j ] = d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] dp[i][j] = dp[i-1][j-w[i]] + v[i] dp[i][j]=dp[i−1][j−w[i]]+v[i]
-
State transition equation: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) , j > = w [ i ] dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]) , j >= w[i] dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i]),j>=w[i]
-
Source code:
// w is the total volume of the backpack // weights represents the weight of an object // values denotes the value of an object and corresponds to weights one by one int backpack(int w, vector<int> &weights, vector<int> &values) { int n = weights.size(); vector<vector<int>> dp(n, vector<int>(w)) for(int i = 0; i < n; ++ i) { for (int j = 0; j < w; ++ j) { if (j >= w[i]) { // The backpack bears enough force to put down the i th object dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]); } else { // The backpack is not strong enough to put down the i-th object dp[i][j] = dp[i-1][j]; } } } return dp[n-1][w-1]; }
Finally: you can optimize the space. I won't go into detail here. Note that the second cycle needs to be flashed and traversed. For details: CyC2018
Complete knapsack problem
-
Concept: it is similar to the 0-1 knapsack problem, but there are countless objects of each weight, so you can repeatedly put objects of a certain value. The ultimate goal is to maximize the total value of the knapsack
-
Idea: the overall idea is the same as 0-1 backpack
- Item i was not added to the backpack, as above, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
- The i-th item is added to the backpack, which is different from the 01 backpack, because there are unlimited items (but note that the weight limit of the schoolbag is limited), so it should not be transferred to dp[i − 1][j − w[i]] but to dp[i][j − w[i]], that is, after loading the i-th item, you can continue to load the second item. d p [ i ] [ j ] = d p [ i ] [ j − w [ i ] ] + v [ i ] dp[i][j] = dp[i][j-w[i]] + v[i] dp[i][j]=dp[i][j−w[i]]+v[i]
-
State transition equation: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − w [ i ] ] + v [ i ] ) , j > = w [ i ] dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]]+v[i]) , j >= w[i] dp[i][j]=max(dp[i−1][j],dp[i][j−w[i]]+v[i]),j>=w[i]
-
Source code:
// w is the total volume of the backpack // weights represents the weight of an object // values denotes the value of an object and corresponds to weights one by one int backpack(int w, vector<int> &weights, vector<int> &values) { int n = weights.size(); vector<vector<int>> dp(n, vector<int>(w)) for(int i = 0; i < n; ++ i) { for (int j = 0; j < w; ++ j) { if (j >= w[i]) { // The backpack bears enough force to put down the i th object dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]] + v[i]); } else { // The backpack is not strong enough to put down the i-th object dp[i][j] = dp[i-1][j]; } } } return dp[n-1][w-1]; }
Similarly, you can optimize the space. It seems that you need to pay attention to: the second cycle traverses in positive order. I don't understand it. For specific reference Complete Backpack - Zhihu
Multiple knapsack problem
- Don't tidy up first. It's a bit of a brain drain
Hybrid backpack or multi-dimensional Backpack
- it's too hard, 🤮 For details, please refer to Nine lecture knapsack problem
1. LeetCode actual combat
reference resources: Li Kou Lan Shan
0. Combinatorial problems
dp[i] += dp[i-num] // The combination problem needs to consider the order between elements, put target in the outer loop and num in the inner loop for i in range(1, target+1): for num in nums:
377. Combined total Ⅳ, 494 Objectives and, 518 Change II
-
Let's show it here 377. Combined total IV , it needs to consider the order between each number, so it is a combination problem
- Use dp[x] to represent the number of schemes where the sum of the selected elements is equal to X. The goal is to find dp[target].
- The boundary of dynamic programming is dp[0]=1. When no element is selected, the sum of elements is 0, so there is only one scheme.
- When 1 < = i < = t a r g e t 1 <= i <= target 1 < = i < = target, if there is an arrangement in which the sum of elements is equal to i, the last element of the arrangement must be an element in the array num. if the last element of the arrangement is num, there must be n u m < = i num <= i Num < = I. for each arrangement where the sum of elements is equal to I − num, an arrangement where the sum of elements is equal to I can be obtained after adding num finally. Therefore, when calculating * * dp[i] * * the sum of all dp[i − num] should be calculated.
- reference resources: Li Kou official - solution
class Solution { public: int combinationSum4(vector<int>& nums, int target) { vector<unsigned long long> dp(target + 1); dp[0] = 1; for (int i = 1; i <= target; ++ i) { for (auto num: nums) { if (i >= num ) { dp[i] += dp[i-num]; } } } return dp[target]; } };
Note: unsigned long is used here to prevent overflow of test data. In fact, this test case is a bit internal, Answer my question
1. True and False problem formulas
dp[i] = dp[i] or dp[i-num]
139. Word splitting, 416 Split equal sum subset
-
Show here 139. Word splitting : dp[i] indicates whether the first I bit of s can be represented by words in wordDict
class Solution { public: bool wordBreak(string s, vector<string>& wordDict) { vector<bool> dp(s.size() + 1); // The set container is used here to facilitate searching unordered_set<string> m(wordDict.begin(), wordDict.end()); dp[0] = true; int maxL = 0; // Here is an optimization. Each time j, you don't need to traverse from 0. There is a string with the minimum length in wordDict for (auto s: wordDict) { maxL = max(maxL, (int)s.length()); } for (int i = 1; i <= s.size(); ++ i) { int start = max(i - maxL, 0); for (int j = start; j < i; ++ j) { if (dp[j] && m.find(s.substr(j, i-j)) != m.end()) { dp[i] = true; break; } } } return dp[s.size()]; } };
2. Minimum problem
dp[i] = min(dp[i], dp[i-num]+1) or dp[i] = max(dp[i], dp[i-num]+1)
474. One and zero, 322 Change
-
Show here 322. Change : keep the change to a minimum
-
Assuming that f(n) represents the minimum number of coins to be used to collect the amount of N, then:
f ( n ) = m i n ( f ( n − c 1 ) , f ( n − c 2 ) , . . . f ( n − c n ) ) + 1 f(n) = min(f(n - c1), f(n - c2), ... f(n - cn)) + 1 f(n)=min(f(n−c1),f(n−c2),...f(n−cn))+1
Where c1 ~ cn are all denominations of coins.
-
Explain this formula in detail. For example:
input: coins = [1, 2, 5], amount = 11 output: 3 explain: 11 = 5 + 5 + 1
The value of the question is f(11). When we choose a coin for the first time, we have three choices.
Suppose we take the coin with the denomination of 1, then the total amount to be rounded up becomes 11 - 1 = 10, that is, f(11) = f(10) + 1, where + 1 is the coin with the denomination of 1.
Similarly, if you take a coin with a denomination of 2 or 5, you can get:
- f(11) = f(9) + 1
- f(11) = f(6) + 1
So:
f(11) = min(f(10), f(9), f(6)) + 1
-
reference resources: Li Kou Jiang Bu
class Solution { public: int coinChange(vector<int>& coins, int amount) { vector<int> dp(amount + 1, INT_MAX); dp[0] = 0; // Both traversal methods are OK for (int i = 1; i <= amount; ++ i) { for (auto c: coins) { if (i >= c && dp[i-c] != INT_MAX) { dp[i] = min(dp[i], dp[i-c] + 1); } } } // for (int c: coins) { // for(int j = c; j <= amount; ++ j) { // if(dp[j- c] != INT_MAX) { // dp[j] = min(dp[j], dp[j-c] + 1); // } // } // } return dp[amount] == INT_MAX ? -1 : dp[amount]; } };
-
2. Trick
reference resources: Li Kou Lan Shan
But I don't seem to understand, 🤒
Knapsack problem skills:
-
If it is a 0-1 knapsack, that is, the elements in the array cannot be reused, Num is placed in the outer loop, target is in the inner loop, and the inner loop is in reverse order;
for num in nums: for i in range(target, nums-1, -1):
-
If it is a complete knapsack, that is, the elements in the array can be reused. Num is placed in the outer loop and target is in the inner loop. And the internal circulation is in positive order.
for num in nums: for i in range(nums, target+1):
-
If the combination problem needs to consider the order between elements, put target in the outer loop and num in the inner loop.
for i in range(1, target+1): for num in nums: