[LeetCode] fully understand the knapsack problem

Posted by juschillin on Sat, 19 Feb 2022 01:13:37 +0100

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

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:
    

Topics: Algorithm leetcode Dynamic Programming