Dynamic programming knapsack problem

Keywords: Algorithm data structure Dynamic Programming

Knapsack problem: there are N items and a knapsack with a capacity of W. each item has its own volume W and value v. find out which items can maximize the total value of the items contained in the knapsack. If only 0 or 1 items can be selected for each item, the problem is called 0-1 knapsack problem; If the quantity of each item is not limited, the problem is called complete knapsack problem.

For the 0-1 knapsack problem, we can 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. When we traverse the ith item, when the total capacity of the current backpack is j, there are two cases: (1) if we do not put the item I into the backpack, that is, the current backpack capacity is insufficient or the maximum value of the current item cannot be reached, then dp[i][j]= dp[i-1][j], that is, the maximum value of the first I items is equal to the maximum value when only the first i -1 items are taken; (2) If we put item I into the backpack, assuming that the volume of item I is w and the value is v, then we get dp[i][j]= dp[i-1][j-W] + v.

int knapsack(vector<int> weights, vector<int> values, int N, int W) {
	vector<vector<int>> dp(N+1,vector<int>(W+1));
    // Place the ith item
    for(int i=1;i<=N;++i){
        // Volume w and value v of the ith article
        int w = weights[i-1], v = values[i-1];
        // ergodic capacity 
        for(int j=1;j<=W;++j){
            if(j>=w){
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v);
            }else{
                dp[i][j] = dp[i-1][j];
            }
        }
    }
    return dp[N][W];
}

In the complete knapsack problem, an item can be taken multiple times. Here, the state transition equation dp[i][j] = max(dp[i-1][j], dp[i][j-w] + v) of the complete knapsack problem is directly given. The difference between it and the 0-1 knapsack problem is that the second i-1 in the state transition equation is changed into I.

int knapsack(vector<int> weights, vector<int> values, int N, int W) {
	vector<vector<int>> dp(N+1,vector<int>(W+1));
    // Place the ith item
    for(int i=1;i<=N;++i){
        // Volume w and value v of the ith article
        int w = weights[i-1], v = values[i-1];
        // ergodic capacity 
        for(int j=1;j<=W;++j){
            if(j>=w){
                dp[i][j] = max(dp[i-1][j],dp[i][j-w]+v);
            }else{
                dp[i][j] = dp[i-1][j];
            }
        }
    }
    return dp[N][W];
}

416 segmentation and subsets

Given an array of positive integers, find out whether the array can be divided into two parts equal to and.

The input is a one-dimensional positive integer array, and the output is a Boolean value, indicating whether it can meet the requirements of the topic.

Input: num = [1,5,11,5]
Output: true
Explanation: an array can be divided into [1, 5, 5] and [11].

Resolution:

This problem is equivalent to the 0-1 knapsack problem. Let the sum of all numbers be sum. Our goal is to select some items so that their sum is target = sum/2, and fill the knapsack with some items. At the same time, this problem does not need to consider value, so we only need to express the state transition matrix through a Boolean matrix.

Setting status: dp[i][j] indicates whether the first I integers of nums can be combined into a sum of j

State transition equation: (01 the subproblems of knapsack problem are very simple, and only distinguish the case of selecting the current item or not)

  • Do not select num [i]: when num [i] is greater than the current capacity j, the capacity is not enough, do not select the item, dp[i][j] = dp[i-1][j]

  • Select nums[i]:

    • Num [i] is exactly equal to the current capacity j, that is, when num [i] = = j, only this item can fill the backpack with capacity j, dp[i][j] =true
    • When num [i] is less than the current capacity J, i.e. num [i] < J, only the item is not enough to fill the backpack with capacity J. check whether the backpack can be filled when DP [I-1] [j-num [i]] is put in the item

    Generally speaking, when the capacity is sufficient, the item num [i] can be optional (if it is valuable, it is necessary to select the case with large value), then DP [i] [J] = DP [I-1] [J] | DP [I-1] [j-num [i]]

Initial condition: when the capacity is 0, no items are selected, dp[i][0] = true; When there is only one item, it is exactly satisfied when the capacity is j = = num [0], that is, DP [0] [num [0]] = true

The state transition matrix of the example can be obtained as follows according to the above state transition equation:

01234567891011
[0] = 1TTFFFFFFFFFF
[1] = 5TTFFFTTFFFFF
[2] = 11TTFFFTTFFFFT
[3] = 5TTFFFTTFFFTT
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(),nums.end(),0);
        auto maxPos = max_element(nums.begin(),nums.end());
        int target = sum/2, len = nums.size();
        // Note that in special cases, if the sum is odd or the number of elements is small, it cannot be effectively divided with 2, and the element value greater than target will directly lead to out of bounds
        if(len<2 || sum&1 || *maxPos > target){
            return false;
        }
        vector<vector<bool>> dp(len,vector<bool>(target+1,false));
        // The initial capacity is 0
        for(int i=0;i<len;++i){
            dp[i][0] = true;
        }
        // Initially, there is only one item
        dp[0][nums[0]] = true;
        for(int i=1;i<len;++i){
            for(int j=1;j<=target;++j){
                // Backpack capacity is larger than the current scanned items, optional
                if(j >= nums[i]){
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
                // The backpack capacity is less than the current scanned items. You can't select it
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[len-1][target];
    }
};

494 objectives and

Define an integer array nums and an integer target for. Add '+' or '-' before each integer in the array, and then concatenate all integers to construct an expression and return the number of different expressions whose operation result is equal to target.

Enter a one-dimensional array and an integer, and output an integer to represent the number of methods whose construction expression result is target

Input: num = [1,1,1,1,1], target = 3
Output: 5
Explanation: there are 5 ways to make the final goal and 3.
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

Resolution:

This problem can be transformed into 01 knapsack problem. Assuming that the sum of array elements is sum, add '+' elements and X, then add '-' elements and sum - X. Thus, we can get x - (sum - x) = target, that is, x = (target + sum) / 2. Then this problem can be transformed into how many methods there are to use N items in num to fill the backpack with capacity X. we can see a combinatorial problem at this time.

Setting status: use a two-dimensional array dp[i][j] to represent the number of methods in which all items in the array nums from the beginning to the end of position I fill the backpack with capacity j.

State transition equation: considering the ith element, if the current knapsack capacity J < nums [i], the ith element dp[i][j] = dp[i-1][j] cannot be placed; If the current knapsack capacity J > = num [i], the number of methods that do not put the ith element is dp[i][j] = dp[i-1][j], and the number of methods that put the ith element is dp[i][j] = dp[i-1][j-num [i]], the total number of methods is the sum of the two cases, that is, dp[i][j] = dp[i-1][j] + DP [I-1] [j-num [i]]

Boundary case: if (target + sum) is not even, there is no solution. If ABS (target) > sum is also no solution, no construction method can meet the problem conditions. When the array is empty and the backpack capacity is 0, dp[0][0] == 1

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int len = nums.size();
        int sum = accumulate(nums.begin(),nums.end(),0);
        if((target+sum)&1 || abs(target)>sum){
            return 0;
        }
        int weight = (target + sum)/2;
        vector<vector<int>> dp(len+1,vector<int>(weight+1));
        dp[0][0] = 1;
        for(int i=1;i<=len;++i){
            for(int j=0;j<=weight;++j){
                dp[i][j] = dp[i-1][j];
                if(j>=nums[i-1]){
                    dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
                }
            }
        }
        return dp[len][weight];
    }
};

474 one and zero

Given m numbers 0, n numbers 1, and some strings composed of 0-1, find out how many given strings can be formed by using these numbers, and the string can be formed only once.

Enter two integers m and n to represent the number of 0 and 1, and a one-dimensional string array to represent the string to be formed; The output is an integer representing the maximum number of strings that can be generated.

Input: strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
Output: 4
Explanation: the largest subset of up to 5 zeros and 3 ones is {"10", "0001", "1", "0"}, so the answer is 4. Other smaller subsets that meet the meaning of the question include {"0001", "1"} and {"10", "1", "0"}. {"111001"} does not satisfy the meaning of the question because it contains four 1s, which is greater than the value 3 of n.

Resolution:

This problem is also a 01 knapsack problem, and its feature is that it has two knapsacks, one for 0 and the other for 1.

Setting status: dp[i][j][k] indicates that several of the first I items can be loaded when the capacity of 0 backpack is j and the capacity of 1 backpack is K

State transition equation: it is still divided into two subproblems: loading the current item and not loading the current item. If not loaded, dp[i][j][k]=dp[i-1][j][k] = max (DP [I-1] [J] [k], DP [I-1] [j-w0 [i] [k-w1 [i]] + 1), where W0 represents the volume of 0 in the item and W1 represents the volume of 1 in the item.

class Solution {
public:

    // Calculate the number of 0 and 1 in each str
    pair<int,int> countWeight(string str){
        int count0 = 0, count1 = 0;
        for(int i=0;i<str.length();++i){
            if(str[i] == '0'){
                ++count0;
            }else{
                ++count1;
            }
        }
        return make_pair(count0,count1);
    }

    int findMaxForm(vector<string>& strs, int m, int n) {
        int len = strs.size();
        // A three-dimensional array is used to represent the maximum number of strings that can be obtained when j zeros and k ones are used in the first i strings
        vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(m+1,vector<int>(n+1)));
        for(int i=1;i<=len;++i){
            auto count = countWeight(strs[i-1]);
            // Both j and k start from zero because str has' 0 'or' 1 ', which is composed of only one element
            for(int j=0;j<=m;++j){
                for(int k=0;k<=n;++k){
                    if(j >= count.first && k >= count.second){
                        dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-count.first][k-count.second] + 1);
                    }else{
                        dp[i][j][k] = dp[i-1][j][k];
                    }
                }
            }
        }
        return dp[len][m][n];
    }
};

322 change

Given the denomination of some coins, find the minimum number of coins that can be used to form a given amount.

Enter a one-dimensional integer array to represent the denomination of coins; And an integer representing the given amount. Output an integer representing the minimum number of coins that meet the condition. If there is no solution, - 1 is returned.

Input: coins = [1, 2, 5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1

Resolution:

Because each coin can be used infinitely many times, this problem is essentially a complete knapsack problem. I still don't understand the knapsack problem completely. Record it and think about it carefully in the future.

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int Max = amount + 1;
        vector<int> dp(amount + 1, Max);
        dp[0] = 0;
        for (int i = 1; i <= amount; ++i) {
            for (int j = 0; j < (int)coins.size(); ++j) {
                if (coins[j] <= i) {
                    dp[i] = min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};

reference material

LeetCode 101: easily brush questions with you (C + +) Chapter 7 deep and simple dynamic programming

Posted by RyanDev on Mon, 27 Sep 2021 21:52:06 -0700