代码随想录算法训练营第43天 | 1049.最后一块石头的重量II, 494.目标和

本文详细解析了如何通过动态规划解决LeetCode中的三道题目:石头碰撞最小重量、构造目标和表达式和二进制字符串最大子集。
摘要由CSDN通过智能技术生成

Leetcode - 1049:最后一块石头的重量II:

题目:

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

示例 1:

输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

示例 2:

输入:stones = [31,26,33,21,40]
输出:5

笔记:

和分割等和子集有点像,这里我们要求最小碰撞重量,所以我们可以将石头凑成两堆重量相近的堆进行碰撞。所以我们就可以求sum/2背包的最大容量,而sum - 最大容量就是另一堆较大的石头堆。所以最终的结果就是sum - dp[n][target] - dp[n][target].接下来使用五部曲:

(1)明确dp数组含义:

dp[i][j] : 表示前i块石头装进背包容量为j的最大容量。

(2)初始化:

只对第一行进行初始化即可:判断当前背包与第一个石头重量的比较。选择是否进行赋值。

(3)状态转移方程:

dp[i][j] = max(dp[i - 1][j - w[i]] + w[i], dp[i - 1][j]);

也是需要提前判断一下背包的容量与石头的重量。

(4)遍历顺序:

更分割等和子集一样我们无需注意顺序问题,因为不论是组合数还是排列数都不影响求最后的最大容量。

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

Leetcode - 494:目标和

题目:

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

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

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

示例 2:

输入:nums = [1], target = 1
输出:1

笔记:

将这堆数据分成两拨,一波是正数,一波是负数,使得这两波数加起来等于target,

那么我们呢根据五部曲逐步分析:

(1)确定dp数组含义:

dp[i][j]表示前i个数字组成j的组合数。

(2)初始化:

dp[0][nums[i]] 赋值为1。

这里有一个重要的点:第一列我们也需要注意:如果nums[i]!=0那么组成0的组合就只有一种如果nums[i] = 0,那么组成的方式就有2种,当连续遇到多个0时就会用2 ^ n种方式。

(3)状态转移方程:

dp[i][j] = dp[i - 1][j](不取) + dp[i - 1][j - nums[i]] + nums[i](取)

(4)遍历顺序:

虽然说是求组合数但是我们细细观察发现这组合是有顺序的,多个组合可以用相同的值来组成但是我们要注意顺序,所以我们需要先遍历物品在遍历背包,内部遍历顺序就从小到大,因为二维数组并不需要记录上一层的数据。

需要额外注意的是:1、当sum  < abs(target) 的时候我们无论怎么组合都不会出现目标

2、当我们的目标数字为小数时也是不会存在目标组合的,也就是正数部分的和为小数。

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

Leetcode - 474:一和零

题目:

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

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

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

示例 1:

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:

输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。

笔记:

(1)明确dp数组含义:

dp[i][j][k]: 表示:前k个元素中i涵盖i个0j个1的最长子集长度。

(2)初始化:

这里需要注意的是dp【0】:用0号字符串去凑成i个0j个1的最大子串长度应该设置为1,并且当背包中o的个数和1的个数>0号字符串中的个数我们就要设置为1.

而不是单单将dp[0][zero[0]][one[0]] 设置为1.

这里与目标和那道题初始化不一样的是:这里每个子集并不为空所以dp[i][0][0] = 1并不能实现。

目标和那到题目对于非0元素dp[i][0] =1是因为我们要凑成目标和:0我们只能选择不选当前元素这一种方案。

这道题我们要注意我们的dp数组含义是长度!!!!!!而不是组合数!!!!!,所以我们的dp[i][0][0]一定是0,因为只有不选择当前元素这一种方案,长度就为0!!!

(3)状态转移方程;

dp[i][j][k] = max(dp[i][j][k - 1], dp[i - zero[k]][j - one[k]][k - 1] + 1)

(4)遍历顺序:

我们求最长子集的长度与顺序无关所以先遍历背包还是先遍历物品都可以。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int n1 = strs.size();
        vector<int> zero(n1 , 0);
        vector<int> one(n1, 0);
        for(int i = 0 ; i < n1 ; i++){
            for(int j = 0 ; j < strs[i]. size(); j++){
                if(strs[i][j] == '0'){
                    zero[i]++;
                }else if(strs[i][j] == '1'){
                    one[i]++;
                }
            }
        }
        vector<vector<vector<int>>> dp(n1, vector<vector<int>>(m + 1, vector<int>(n + 1, 0)));
        for (int i = zero[0]; i <= m; i++) {
            for (int j = one[0]; j <= n; j++) {
                dp[0][i][j] = 1;
            }
        }
        
        for(int i = 1; i < n1; i++){
            for(int j = 0; j <= m; j++){
                for(int k = 0; k <= n; k++){
                    if(zero[i] > j || one[i] > k){
                        dp[i][j][k] = dp[i - 1][j][k];
                    }
                    else{
                        dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - zero[i]][k - one[i]] + 1);
                    }
                }
            }
        }
        return dp[n1 - 1][m][n];
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值