0-1背包问题

问题描述:N种物品,每种物品只有1个,每个物品有自己的重量和价值,有一个最多只能放重量为M的背包。问:这个背包最多能装价值为多少的物品?

二维dp数组解法:

  1. dp数组的含义:dp[i][j]表示下标为0-i(物品的编号)之间的物品任取,放进容量为j的背包里的最大价值;
  2. 递推公式:dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
  3. 初始化:dp[i][0] = 0, dp[0][j] = value[0](如果j < value[0]则初始化为0), dp[0][0] = 0; others[i][j] = 任意值;
  4. 遍历顺序:先遍历物品或者背包都可以。

优化为一维dp数组:

  1. dp数组含义:容量为j的背包最大价值为dp[j];
  2. 递推公式:dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
  3. 初始化:dp[j] = 0;
  4. 遍历顺序:先正序遍历物品,再倒叙遍历背包(保证每个物品只被添加1次)。

LeetCode题目:

分割等和子集icon-default.png?t=N7T8https://leetcode.cn/problems/partition-equal-subset-sum/

  • 容量为j,最大价值为dp[j],目标价值是dp[sum/2];
  • dp[j] = max(dp[j], dp[j-nums[i]] + nums[i]);
  • dp[j] = 0;
  • 先正序遍历i(物品),再倒叙遍历j(背包)。
class Solution {
public:
    bool canPartition(vector& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 != 0)
            return false;
        int target = sum / 2;
        vector dp(target + 1, 0);

        for (int i = 0; i < nums.size(); i++) {
            for (int j = target; j >= nums[i]; j--) {
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }

        if (dp[target] == target)
            return true;
        return false;
    }
};

最后一块石头的重量IIicon-default.png?t=N7T8https://leetcode.cn/problems/last-stone-weight-ii/description/

  • 分成两堆近似重量的粉碎,背包容量为j,能装的最大价值(重量)为dp[j];
  • dp[j] = max(dp[j], dp[j-stones[i]] + stones[i]);
  • dp[j] = 0;
  • 先遍历i(物品),再遍历j(背包)。
class Solution {
public:
    int lastStoneWeightII(vector& stones) {
        int sum = accumulate(stones.begin(), stones.end(), 0);
        int target = sum / 2;
        vector dp(target + 1, 0);

        for (int i = 0; i < stones.size(); i++) {
            for (int j = target; j >= stones[i]; j--) {
                dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }

        return sum - dp[target] - dp[target];
    }
};

目标和icon-default.png?t=N7T8https://leetcode.cn/problems/target-sum/description/

  • 装满容量为j的背包有dp[j]种方法;
  • dp[j] += dp[j-nums[i]];
  • dp[0] = 1, dp[>0] = 0;
  • 先正序遍历i(物品),再倒序遍历j(背包容量)。

本题dp数组怎么来的?

sum(P) - sum(N) = target

sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)

2 * sum(P) = target + sum(nums)

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

一和零icon-default.png?t=N7T8https://leetcode.cn/problems/ones-and-zeroes/

  • 装满i个0,j个1,子集长度最大为dp[i][j],每个子集里面有x个0,y个1(物品重量);
  • dp[i][j] = max(dp[i-x][j-y] + 1, dp[i][j]);
  • dp[i][j] = 0;
  • 先正序遍历物品(strs),再遍历每个strs中的字符——确定每个字符串中的0,1个数,倒序遍历背包容量(m,n)。
class Solution {
public:
    int findMaxForm(vector& strs, int m, int n) {
        vector> dp(m + 1, vector(n + 1, 0));
        for (auto stri : strs) {
            int x = 0, y = 0;
            for (auto chi : stri) {
                if (chi == '0') {
                    x++;
                } else {
                    y++;
                }
            }
            for (int i = m; i >= x; i--) {
                for (int j = n; j >= y; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - x][j - y] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

你可能感兴趣的:(算法,算法,动态规划,c++)