代码随想录算法训练营第43天 | 动态规划 part05 ● 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

文章探讨了几种基于背包问题的动态规划解题方法,包括将最后一块石头的重量问题转化为接近平均重量的两组,以及目标和问题和一和零问题的解法。强调了转换问题思维、初始化DP数组和遍历顺序的重要性,并提供了不同维度DP的实现示例。
摘要由CSDN通过智能技术生成

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

自己没想出来呜呜。我思考的时候以为是有特定顺序才行,然后回想一下背包问题好像不能解决顺序的,只能解决组合的,也就是每个东西有或者没有,然后我就觉得我想不出来了。这个时候我应该联想到背包问题只能解决组合的,所以这道题我那样想顺序的想法就是错的,要转变思路

看了随想录学会了,自己写了:

先用数学想一下 n 个 (y-x)相加,即这些y之和 - 这些x之和 =》分成重量尽可能接近的两堆 =》这题就和416. 分割等和子集  变成几乎一样的了。两堆石头数量不一样也没关系,关键是重量和

注意一维的初始化就包含在那两层for loop里面 i = 0的时候了。

另外dp[target] 代表的是 在target为上限的情况下,能达到的最大重量和(接近sum/2的,所以也是小半,所以最后return是被减的)

int lastStoneWeightII(vector<int>& stones) {
   
        int sum=accumulate(stones.begin(),stones.end(),0);
        int target = sum / 2;
        vector<int> 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];
        
        
    }

又用二维写了一遍,注意二维不适合把初始化也放到那个两层for loop中,因为二维会有i-1的写法,需要特殊处理,一处理就和单独初始化没差别了。这是我写的二维

int lastStoneWeightII(vector<int>& stones) {
        
        int sum=accumulate(stones.begin(),stones.end(),0);
        int target = sum / 2;
        vector<vector<int>> dp(stones.size(),vector<int>(target+1,0));

        //initialize
        for(int j=stones[0];j<=target;j++) dp[0][j]=stones[0];

        for (int i = 1; i < stones.size(); i++) { 
            for (int j = 1; j <=target; j++) { 
                if(j>=stones[i]){
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]);
                }
                else dp[i][j] = dp[i - 1][j];
            }
        }
        return (sum - dp[stones.size()-1][target]) - dp[stones.size()-1][target];
        
    }

这是gpt写的,把初始化放到一起的:

int lastStoneWeightII(vector<int>& stones) {
    int sum=accumulate(stones.begin(),stones.end(),0);
    int target = sum / 2;
    vector<vector<int>> dp(stones.size(),vector<int>(target+1,0));

    for (int i = 0; i < stones.size(); i++) { 
        for (int j = 0; j <=target; j++) { 
            if (i == 0){
                dp[i][j] = (j >= stones[i]) ? stones[i] : 0;
            }else{
                if(j >= stones[i]){
                    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - stones[i]] + stones[i]);
                }else{
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
    }
    return sum - 2 * dp[stones.size()-1][target];
}

#494 目标和 

审题注意看下面Constraints target可能为负数

这一步我想出来了,然后确定了是背包变形,然后我的思路就歪了。其实我也没太看懂gpt说的我哪里错了,但是我自己试了20min写实现,我的思路确实不对。

 这题回溯思路是没错,但是会超时


现在来看随想录的背包思路:

 2x=target+sum 所以右边这俩为奇数无解。或者target abs>sum 也无解。

这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。本题则是装满有几种方法。其实这就是一个组合问题了。

1.确定dp数组和下标的含义: dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法

其实也可以使用二维dp数组来求解本题,dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。

2.递推公式:假设可供选择的是 1 2 3 4 5(现在没有负数了) dp[5](构成答案是5有几种情况)= 选择了1+ dp[5-1] 或 选择了 2 +dp [5-2] 。。。就是每个可供选的都选一次的情况列出来

所以dp[j]+=dp[j-nums[i]] 随想录说这个公式在后面在讲解背包解决排列组合问题时还会用到!

3.初始化:看他写的有点晕,但基本理解了dp[0]=1  

 4. 确定遍历顺序:就正常顺序 5. 略

自己写了一遍代码:

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

#474.一和零  

没想出来,以为要用3d的dp表,还以为dp里面要存set,都不对。算是自己没学过的特殊的解法,学学看吧。随想录思路如下:

就因为这背包的两个维度,本题必须是2d的dp 。i j 分别是 0的个数,1的个数。就像两个一维的背包解法套在一起了。所以遍历也要按1d背包的从后往前。for 物品 for i for j ,三个for loop套在一起。其实我想的3d dp表也算是对的,但是我们要把2d降成1d就变成正确的思路了。weight的限制(背包容量)是两个维度

    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); 
        for (string &str : strs) { 
            int oneNum = 0, zeroNum = 0;
            for (char &c : str) {//相当于把str的两个重量维度算出来
                if (c == '0') zeroNum++;
                else oneNum++;
            }
            for (int i = m; i >= zeroNum; i--) {//对比标准是>=weight【0】 
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }

自我总结:

● 1049. 最后一块石头的重量 II:转化成把石头尽可能分成重量接近sum/2两堆,推导公式就是普通的
● 494. 目标和:也是转化 sum1=(sum+target)/2 ,然后是求和为sum1有多少种组合,dp[j]放组合数,dp[0]=1, dp[j]+=dp[j-nums[i]]
● 474.一和零: 特殊在重量和背包容量,有两个维度衡量。两个1d套在一起记得都是倒序遍历,总共三个for loop,最外是物品

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值