【算法】动态规划系列(二)

文章目录

  • 参考资料
    • 二维数组
      • 62.不同路径
      • 63. 不同路径 II
    • 排列还是组合
    • 背包问题
      • 思路
      • 应用
      • 01背包
        • 核心代码
        • 状态压缩
      • 01背包变式 416. 分割等和子集
      • 1049. 最后一块石头的重量 II
      • 494. 目标和
      • 474. 一和零
      • 完全背包问题
      • 思想
      • 核心代码
      • 518. 零钱兑换 II
      • 377. 组合总和 Ⅳ

参考资料

labuladong 的算法小抄
代码随想录

二维数组

62.不同路径

62.不同路径

代码

class Solution {
  /**
     * 1. 确定dp数组下表含义 dp[i][j] 到每一个坐标可能的路径种类
     * 2. 递推公式 dp[i][j] = dp[i-1][j] dp[i][j-1]
     * 3. 初始化 dp[i][0]=1 dp[0][i]=1 初始化横竖就可
     * 4. 遍历顺序 一行一行遍历
     * 5. 推导结果 。。。。。。。。
     *
     * @param m
     * @param n
     * @return
     */
    public static int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        //初始化
        for (int i = 0; i < m; i++) {
            dp[i][0] = 1;
        }
        for (int i = 0; i < n; i++) {
            dp[0][i] = 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];
    }
}

63. 不同路径 II

63. 不同路径 II

代码

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if(obstacleGrid.length<0){
            return 0;
        }
        int n = obstacleGrid.length;
        int m = obstacleGrid[0].length;
        int[][] dp = new int[n][m];
        // 初始化
        for(int i=0;i<n;i++){
            if(obstacleGrid[i][0]==1){
                break;
            }
            dp[i][0]=1;
        }
        for(int j=0;j<m;j++){
            if(obstacleGrid[0][j]==1){
                break;
            }
            dp[0][j]=1;
        }
        for(int i=1;i<n;i++){
            for(int j=1;j<m;j++){
                if(obstacleGrid[i][j]==0){
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[n-1][m-1];
    }
}

排列还是组合

希望用一种规律搞定背包问题

解题思路
常见的背包问题有1、组合问题。2、True、False问题。3、最大最小问题。
以下题目整理来自大神CyC,github地址:
github
我在大神整理的基础上,又做了细分的整理。分为三类。
1、组合问题:
377. 组合总和 Ⅳ
494. 目标和
518. 零钱兑换 II
2、True、False问题:
139. 单词拆分
416. 分割等和子集
3、最大最小问题:
474. 一和零
322. 零钱兑换

组合问题公式
dp[i] += dp[i-num]
True、False问题公式
dp[i] = dp[i] or dp[i-num]
最大最小问题公式
dp[i] = min(dp[i], dp[i-num]+1)或者dp[i] = max(dp[i], dp[i-num]+1)
以上三组公式是解决对应问题的核心公式。

作者:Jackie1995
链接:https://leetcode-cn.com/problems/combination-sum-iv/solution/xi-wang-yong-yi-chong-gui-lu-gao-ding-bei-bao-wen-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

背包问题

背包问题,大家都知道,有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
状态:

  1. 背包容量
  2. 物品
    选择:选不选该物品。

思路

  1. 01背包还是完全背包
    决定内层循环是正序还是逆序
  2. 最值问题还是组合数量问题,如果用一维数组。
    如果是组合数量问题,考虑物品跟容量的遍历顺序,
    如果先遍历物品,再遍历容量,那么求出来的是组合数。
    如果先遍历容量,再遍历物品,那么求出来的是排列数。
    其他问题不需要考虑这种遍历顺序。
  3. 考虑dp[0]的值应该是多少。
    最值问题:dp[0]=0,其他非零为最大值或者最小值。
    组合问题:dp[0]=1
    true、false问题: dp[0] = true;

应用

  1. 给定一组物品,选择最大价值。
一般公式都是:dp[j] = Math.max(dp[j],dp[j - nums[i]]);
  1. 给定一组物品,选择组合数量。
求装满背包有几种方法,一般公式都是:dp[j] += dp[j - nums[i]];

01背包

遍历顺序,
一维数组:
先遍历物品,再遍历容量
二维数组:
都可以

核心代码

状态压缩

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

这里遍历容量的时候是从大到小遍历,因为从小到大遍历使用了覆盖后的元素进行计算,导致结果不准备,或者说是物品被放入了两次。

01背包变式 416. 分割等和子集

416. 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

思路
状态:

  1. 背包的容量
  2. 物品

代码

class Solution {
    /**
     * 背包的容量跟可选择的物品就是状态
     */
    boolean canPartition(int[] nums) {
        int sum=0;
        for(int num:nums){
            sum += num;
        }
        if(sum%2!=0){
            return false;
        }
        int n = nums.length;
        sum = sum/2;
        boolean[][] dp = new boolean[n+1][sum+1];
        for(int i=0;i<=n;i++){
            dp[i][0]=true;
        }
        for(int i=1;i<=n;i++){
            for(int j=0;j<=sum;j++){
                if(j-nums[i-1]>=0){
                    dp[i][j]= dp[i-1][j] || dp[i-1][j-nums[i-1]];
                }else{
                    dp[i][j]= dp[i-1][j];
                }
            }
        }
        return dp[n][sum];
    }
    }

1049. 最后一块石头的重量 II

1049. 最后一块石头的重量 II

转换为01背包问题

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int n = stones.length;
        int sum =0 ;
        for(int stone:stones){
            sum += stone;
        }
        int target = sum / 2; // 将石头分成两堆
        int[][] dp = new int[n+1][target+1];
        for(int i=1;i<=n;i++){
            for(int j=0;j<=target;j++){
                if(j-stones[i-1]>=0){
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-stones[i-1]]+stones[i-1]);
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return sum - dp[n][target]-dp[n][target];

    }
}

一位数组

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int n = stones.length;
        int sum =0 ;
        for(int stone:stones){
            sum += stone;
        }
        int target = sum / 2;
        int[] dp = new int[target+1];
        for(int i=1;i<=n;i++){
            for(int j=target;j>=stones[i-1];j--){// 每一个元素一定是不可重复放入,所以从大到小遍历 物品 i 的重量是 nums[i],其价值也是 nums[i]
                dp[j]=Math.max(dp[j],dp[j-stones[i-1]]+stones[i-1]);
            }
        }
        return sum - dp[target]-dp[target];

    }
}

494. 目标和

494. 目标和

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目

思路

代码

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int n = nums.length;
        int sum = 0;
        for(int num:nums){
            sum += num;
        }
        if((sum+target)%2!=0){
            return 0;
        }
        target = (sum+target)/2;
        if(target<0){
            target= -target;
        }
        int[][] dp = new int[n+1][target+1];
        for(int i=0;i<=n;i++){
            dp[i][0]=1;
        }
        for(int i=1;i<=n;i++){
            for(int j=0;j<=target;j++){
                if(j-nums[i-1]>=0){
                   dp[i][j] = dp[i-1][j]+ dp[i-1][j-nums[i-1]];
                }else{
                    dp[i][j] =dp[i-1][j];
                }
            }
        }
        return dp[n][target];
    }
}

二维数组

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.length; i++) sum += nums[i];
        if ((target + sum) % 2 != 0) return 0;
        int size = (target + sum) / 2;
        if(size < 0) size = -size;
        int[] dp = new int[size + 1];
        dp[0] = 1;
        for (int i = 0; i < nums.length; i++) {
            for (int j = size; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[size];
    }
}

474. 一和零

474. 一和零

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

思路
物品:字符串
背包容量:0和1的数量

代码

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        //dp[i][j]表示i个0和j个1时的最大子集
        int[][] dp = new int[m + 1][n + 1];
        int oneNum, zeroNum;
        for (String str : strs) {
            oneNum = 0;
            zeroNum = 0;
            for (char ch : str.toCharArray()) {
                if (ch == '0') {
                    zeroNum++;
                } else {
                    oneNum++;
                }
            }
            //倒序遍历
            for (int i = m; i >= zeroNum; i--) {
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }
}

完全背包问题

思想

遍历顺序考究。

  1. 如果先遍历物品,再遍历容量,那么求出来的是组合数。
  2. 如果先遍历容量,再遍历物品,那么求出来的是排列数。

核心代码

而完全背包的物品是可以添加多次的,所以要从小到大去遍历。
纯背包问题,遍历顺序可以调换。

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j < bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

518. 零钱兑换 II

518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

思路

完全背包问题:每个物品都可以无限选取。
遍历顺序:

  1. 先物品,再容量,求得是组合数。

代码

class Solution {
        public int change(int amount, int[] coins) {
            int n = coins.length;
            int[][] dp = new int[n + 1][amount + 1];
            for (int i = 0; i <= n; i++) {
                dp[i][0] = 1; // base case:无为而治
            }
            for (int i = 1; i < n + 1; i++) {
                for (int j = 0; j < amount + 1; j++) {
                    if (j - coins[i - 1] >= 0) {
                        dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];// 注意不是dp[i][j] = dp[i - 1][j] + dp[i-1][j - coins[i - 1]] ,区别于0,1背包
                    } else {
                        dp[i][j] = dp[i - 1][j] ;                        
                    } 
                }
            }
            return dp[n][amount];
        }
}
class Solution {
    public int change(int amount, int[] coins) {
            int[] dp = new int[amount + 1];
            dp[0] = 1;
            for(int coin : coins){ // 枚举物品
                for (int j = 1; j < amount + 1; j++) { // 枚举金额,从小到大进行遍历。表示可以取无限次数
                    if (j >= coin) {
                        dp[j] += dp[j - coin];
                    }
                }
            }
            return dp[amount];
    }
}

377. 组合总和 Ⅳ

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。

参考资料

思路
本题与爬楼梯问题类似。
完全背包问题,排列问题。
遍历顺序:

  1. 先遍历背包,再遍历物品,得到排列数。

也可以用普通的动态规划去解释。
代码

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int n = nums.length;
        int[] dp = new int[target+1];
        dp[0]=1;
        for (int i = 0; i <= target; i++) {
            for (int j = 0; j < nums.length; j++) {
                if (i >= nums[j]) {
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
}

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