代码随想录算法训练营第四十三天| LeetCode 1049.最后一块石头的重量II、494. 目标和、474.一和零

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

文章讲解/视频讲解:https://programmercarl.com/1049.%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8FII.html#%E6%80%9D%E8%B7%AF

状态:已解决

1.思路 

        其实这个题跟上个题416. 分割等和子集 - 力扣(LeetCode)很像,我们可以把整个石头分成两堆,为了使碰撞后剩余石头重量最小,那么就尽量使两堆石头的重量接近,怎么才能使两堆石头重量尽量接近呢?努力凑齐某堆石头接近sum/2即可。那么这道题就转换成了昨天的416题。具体分析看LeetCode 416.分割等和子集-CSDN博客

        这题最后要求最小的石头重量,那么我们装sum/2的背包时,得到的价值(重量)dp[n]是 <= sum/2。故剩下的那堆石头价值(重量)>= dp[n],也就是sum-dp[n] >= dp[n]。故碰撞后剩余石头重量为 (sum-dp[n]) - dp[n]。

2.代码实现

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;
        for(int i=0;i<stones.size();i++){
            sum += stones[i];
        }
        int m = stones.size(),n = sum/2;
        vector<int> dp(n+1,0);
        vector<bool> flag(m,false);
        for(int i=0;i<m;i++){
            for(int j=n;j>=stones[i];j--){
                dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return (sum-dp[n])-dp[n];
    }
};

二、494. 目标和

文章讲解/视频讲解:https://programmercarl.com/0494.%E7%9B%AE%E6%A0%87%E5%92%8C.html

状态:已解决

1.思路 

        这道题其实是有点难想的,最直接的思路就是回溯。那采用动规又该如何解决呢?我们注意看,我们向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数后,得到的实则为一堆数减去另一堆数的表达式。我们将所有的被减数作为一个整体,设为left,将所有的减数作为一个整体,设为right。由于此题的数组nums依旧是一开始都给定的,且要求每个数都要参与计算,那么left 和 right 的和就是固定的sum(nums中所有元素的和)。即:

left + right = sum;

left - right = target;

        解得:

 left = (sum + target)/2;

        由于 sum 和 target 都是样例给定的固定值,因此left也是固定值。故只需找到原数组有多少子集和为left,就可得到满足条件的表达式个数。那么此题就变成了一个背包问题:背包容量为left,物品数为nums.size(),重量为nums[i]。

(1)确定dp数组以及下标的含义:

        dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法。

(2)确定递推公式:

        有哪些来源可以推出dp[j]呢?假如遍历到nums[i]时,那么凑成dp[j]就要看dp[j - nums[i]] 有多少种方法。因为nums有多个,那么每次遍历一个nums[i]都有一个对应相等的 j,故总的dp[j]就为所有j - nums[i] 的和。即dp[j] += dp[j - nums[i]]。

例如:dp[j],j 为5,

  • 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
  • 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
  • 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
  • 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
  • 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包

那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。所以求组合类问题的公式,都是类似这种

(3)dp数组初始化:

        如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。所以本题我们应该初始化 dp[0] 为 1。

(4)确定遍历顺序:

        在一维背包-CSDN博客中,我们讲过对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。

(5)举例推导dp数组:

        输入:nums: [1, 1, 1, 1, 1], S: 3;bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4

        dp数组状态变化如下:

2.代码实现 

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

时间复杂度:O(n × m),n为正数个数,m为背包容量

空间复杂度:O(m),m为背包容量

三、474.一和零

文章讲解/视频讲解:https://programmercarl.com/0474.%E4%B8%80%E5%92%8C%E9%9B%B6.html

状态:已解决

1.思路  

        这道题其实就是在01背包问题的基础上,将背包的维度上升了。也就是,决定背包容量的属性不仅只有重量一栏了,现有0的数量和1的数量两栏,二者共同决定了背包容量。那么这道题就转换成了一个背包问题:容量由m和n决定,物品有strs.size()个,每个物品的重量也是两个维度(0的个数和1的个数),每个物品的价值为1,现求该背包最多能装多少物品。

(1)确定dp数组以及下标:

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。

(2)确定递推公式:

跟一维类似,dp[i][j]由没添加strs[i]的左上角状态而来,即dp[i][j] = dp[i - zeroNum][j - oneNum] + 1(因为strs[i]加入了子集,故子集大小+1) 。由于可能部分集合strs[i] 0和1的个数相同,故dp[i][j]需要一直维护最大的dp[i - zeroNum][j - oneNum] + 1。

(3)dp数组的初始化:

跟前面的题没差,01背包的dp数组(滚动数组)初始化为0就可以。因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

(4) 确定遍历顺序:

还是跟前面的题一样,最外层物品正序,内层(两层)都是倒序。

(5) 举例推导dp数组:

以输入:["10","0001","111001","1","0"],m = 3,n = 3为例

最后dp数组的状态如下所示:

2.代码实现 

class Solution {
public:
    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 zeroNum = 0,oneNum = 0;
            for(int i=0;i<str.size();i++){
                if(str[i] == '0') zeroNum++;
                else oneNum++;
            }
            for(int i = m;i >= zeroNum; i--){
                for(int j = n; j >= oneNum; j--){
                    dp[i][j] = max(dp[i-zeroNum][j-oneNum]+1,dp[i][j]);
                }
            }
        }
        return dp[m][n];
    }
};

 时间复杂度: O(kmn),k 为strs的长度

空间复杂度: O(mn)

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值