力扣打卡day19

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

本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
本题物品的重量为stones[i],物品的价值也为stones[i]。
对应着01背包里的物品重量weight[i]和 物品价值value[i]。
接下来进行动规五步曲:
确定dp数组以及下标的含义
dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背dp[j]这么重的石头。
2.确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题则是:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
3、dp数组初始化
提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了。
4.确定遍历顺序
和正常的遍历一致
在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的。
那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。
最后dp[target]里是容量为target的背包所能背的最大重量。
那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int[] dp=new int[15001];
        int sum=0;
        for(int num:stones){
            sum+=num;
        }
        int target=sum/2;
        int n=stones.length;
        for(int i=0;i<n;i++){
            for(int j=target;j>=stones[i];j--){
                dp[j]=Math.max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum-dp[target]-dp[target];
    }
}

322. 零钱兑换

确定dp数组以及下标的含义
dp[j]:凑足总额为j所需钱币的最少个数为dp[j]

2.确定递推公式

dp[j - coins[i]](没有考虑coins[i])。
凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])

所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。

递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

3.dp数组初始化

dp[0]=0

dp[j]必须初始化一个最大的书否则就会在递推公式中比较的过程被初始值覆盖

4.确定遍历顺序

本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。
本题并不强调集合是组合还是排列。

如果求组合数就是外层for循环遍历物品,内层for遍历背包。

如果求排列数就是外层for遍历背包,内层for循环遍历物品。所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!

本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序

class Solution {
    public int coinChange(int[] coins, int amount) {
        int m=Integer.MAX_VALUE;
        int[] dp=new int[amount+1];
        for(int j=0;j<dp.length;j++){
            dp[j]=m;
        }
         dp[0]=0;
        for(int i=0;i<coins.length;i++){
            for(int j=coins[i];j<=amount;j++){
              if (dp[j - coins[i]] != m) {
                    //选择硬币数目最小的情况
                    dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
                }
            }
        }
        return dp[amount]==m?-1:dp[amount];
    }
}

494. 目标和

假设加法的总和为x,那么减法对应的总和就是sum - x。

所以我们要求的是 x - (sum - x) = S
确定dp数组以及下标的含义
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法

2.确定递推公式

不考虑nums[i]的情况下,填满容量为j的背包,有dp[j]种方法。

那么考虑nums[i]的话(只要搞到nums[i]),凑成dp[j]就有dp[j - nums[i]] 种方法。
3.dp数组如何初始化
dp[0]=1,dp[j]其他下标对应的数值应初始化为0
4. 确定遍历顺序
对于01背包问题一维dp的遍历,nums放在外循环,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 == 1) return 0; // 此时没有方案
        if (Math.abs(target) > sum) return 0; // 此时没有方案
        int bagSize=(sum+target)/2;
        if(bagSize<0) return 0;
        int[] dp=new int[bagSize+1];
        for(int j=0;j<bagSize;j++){
            dp[j]=0;
        }
        dp[0]=1;
        for(int i=0;i<nums.length;i++){
            for(int j=bagSize;j>=nums[i];j--){
                dp[j]+=dp[j-nums[i]];
            }
        }
        return dp[bagSize];
    }
}

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