dp

https://leetcode.cn/problems/unique-paths-ii/submissions/639090270/

  1. 确定dp数组(dp table)以及下标的含义
    即:从0,0 出发到i,j的不同路径:dp[i][j]

  2. 确定递推公式
    即dp[i][j]能由什么得到,比如:dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

  3. dp数组初始化
    如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

  4. 确定遍历顺序
    比如从左到右一层层遍历,保证递推的来源:dp[i - 1][j] + dp[i][j - 1]都是存在的

例如:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for (int i = 0; i < m; i++) dp[i][0] = 1;
        for (int j = 0; j < n; j++) dp[0][j] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};

也可以构造dfs(起点,终点)
给出起点和终点:在超出边界时return 0;在找到终点时return 1;其他都return dfs之和(一次变化后的起点,终点)
例如:

class Solution {
private:
    int dfs(int i, int j, int m, int n) {
        if (i > m || j > n) return 0; // 越界了
        if (i == m && j == n) return 1; // 找到一种方法,相当于找到了叶子节点
        return dfs(i + 1, j, m, n) + dfs(i, j + 1, m, n);
    }
public:
    int uniquePaths(int m, int n) {
        return dfs(1, 1, m, n);
    }
};

01 背包
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

  1. dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,时的最大总和价值

  2. 递推公式
    对于第一个可以递推得到的,dp[1][w]
    很重要的是这个在最大容量下任务0-1物品的情况,
    两种情况,分别是放物品1 和 不放物品1,我们要取最大值(毕竟求的是最大价值)

dp[1][4] = max(dp[0][4], dp[0][1] + 物品1 的价值)

以上过程,抽象化如下:

  • 不放物品i:背包容量为j,里面不放物品i的最大价值是dp[i - 1][j]。

  • 放物品i:背包空出物品i的容量后,背包容量为j - weight[i],dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]且不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

  1. dp数组初始化(i<n,j<=w)
    当j=0,则不可能放入,dp[i][0]=0;当i=0;则当j>=weight[0]时,dp[0][j]=value[0]
    详见:https://www.programmercarl.com/背包理论基础01背包-1.html#思路

https://leetcode.cn/problems/ones-and-zeroes/

https://leetcode.cn/problems/partition-equal-subset-sum/

class Solution {
public:
//
    bool canPartition(vector<int>& nums) {
        int n=nums.size();
        if(n<2){
            return false;

        }
        int sum=accumulate(nums.begin(),num.end(),0);


        //一些简单的条件排除
        int maxNum = *max_element(nums.begin(), nums.end());
        if (sum & 1) {
            return false;
        }
        int target = sum / 2;
        if (maxNum > target) {
            return false;
        }

        //每一层是不断扩大的j,最终到背包容量
        //一层一层是不断增加的物品选择,
        vector<vector<int>> dp(n, vector<int>(target + 1, 0));

        //初始化理解:true的定义是j能否等于能选的0-i的组合的重量
        //所以第一列j=0,0-i可以都不选,故都为true
        //而对于第0号物品,只有第weight【i】列满足。
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }
        dp[0][nums[0]] = true;

        //对于每一个dp[i][j]都先判断nums[i](其实也就是weight[i]会不会超出容量,也就是每层不断增加的j直到sum/2)(

        //只有当容量j大于weight时才能选择加入或不加入dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];;不然只能被迫等于上一行,即不选择本nums[i]的加入dp[i - 1][j] 。)(而选择可能性也来源于之前不选择i时,上限-nums[i]的情况)
        for (int i = 1; i < n; i++) {
            int num = nums[i];
            for (int j = 1; j <= target; j++) {
                if (j >= num) {
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
    return dp[n-1][target];


    }
};

第二种:滚动数组dp[j]
原递推:

if (j >= num) {
                        dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                    } else {
                        dp[i][j] = dp[i - 1][j];
                    }

即dp[i][j]都只与上一行i-1的dp值有关,所以可以只记录j,即:dp[j]=dp[j] ∣ dp[j−nums[i]]

但是记得这种形式的遍历一定要倒序:因为这个公式会让它选择等于之前选择过的值dp[j−nums[i]]
。而零一背包不能重复选择,故从target开始,减小到nums[i](因为只有大于nums[i]才能选择,这个数组才有变化的意义)
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int i = 0; i < n; i++) {
int num = nums[i];
for (int j = target; j >= num; --j) {
dp[j] |= dp[j - num];
}
}

递推规则一般有两种:
二维DP数组递推公式: dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];

去掉维度i 之后,递推公式:

dp[j] = dp[j] + dp[j - nums[i]] ,即:dp[j] += dp[j - nums[i]]


dp[i][j] = max(dp[i - 1][j] , dp[i - 1][j - nums[i]+value[i]);
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。

所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

一、两种公式的适用场景

dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,时的最大总和价值
1. dp[j] += dp[j - nums[i]]:组合计数问题
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];

问题定义:
给定一组物品(每个物品的重量为 nums[i]),求装满容量为 j 的背包有多少种不同的组合方式。
例如:nums = [1, 2],容量 j = 3,则组合方式有 [1,1,1]、[1,2],共 2 种。
状态含义:
dp[j] 表示装满容量为 j 的背包的组合数。
转移方程推导:
对于第 i 个物品,若选择它,则组合数为 dp[j - nums[i]];若不选择,则组合数为 dp[j]。因此总组合数为两者之和。

2. dp[j] = max(dp[j], dp[j - weight[i]] + value[i]):价值最大化问题

来源于:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

问题定义:
给定一组物品(每个物品的重量为 weight[i],价值为 value[i]),在背包容量限制为 j 的情况下,求能装入的最大价值。
例如:weight = [2, 3], value = [4, 5],容量 j = 5,则最大价值为 4 + 5 = 9(选两个物品)。
状态含义:
dp[j] 表示容量为 j 的背包能装入的最大价值。
转移方程推导:
对于第 i 个物品,若选择它,则价值为 dp[j - weight[i]] + value[i];若不选择,则价值为 dp[j]。取两者的最大值。

总结
要先定义dp[i][j]数组的意义,然后读题来得到容量,以及判断是价值最大化还是组合数问题来判断递推公式,然后根据容量初始化数组并初始化第一行、第一列。最后注意第二层循环时要从容量往下遍历到nums[i]