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

文章介绍了力扣上的三道与背包问题相关的编程题目,分别是:1)将石头尽可能平均分成两堆,求剩余最小重量;2)求解数组子集和等于目标值的方案数;3)二维背包问题,求装满背包的物品最大数量。每道题都使用了动态规划的方法,通过定义dp数组和递推公式来解决,并给出了具体的代码实现。
摘要由CSDN通过智能技术生成

最后一块石头的重量 :给定背包容量,尽可能装,最多能装多少

题目链接:力扣

 本题的实质是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。

本题物品的重量为stones[i],物品的价值也为stones[i]。
对应着01背包里的物品重量weight[i]和 物品价值value[i]。

  1. 确定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数组初始化 
    dp[j]中的j表示容量,那么最大容量(重量)是所有石头的重量和。
    提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 
    而我们要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了.
    dp[j]都初始化为0即可
  4. 遍历顺序
    遍历背包的for循环放在内层,且内层for循环倒序遍历
  5. 举例推导dp数组

最后dp[target]里是容量为target的背包所能背的最大重量。
那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。
在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的
那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
         int target = 0;
         int sum = 0;

         for(int i=0;i<stones.size();i++)
         {
             sum += stones[i];
         }

         target = sum/2;

         vector<int>dp(100*30/2+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];

    }
};

目标和: 给定背包容量,装满背包有多少种方法。

题目链接:力扣

这题 乍一看和背包问题联系不起来,其实需要先进行一番推导,摘抄来自leetcode的评论。

原问题等同于: 找到nums一个正子集和一个负子集,使得总和等于target

我们假设P是正子集,N是负子集
例如: 假设nums = [1, 2, 3, 4, 5],target = 3,
一个可能的解决方案是+1-2+3-4+5 = 3 这里正子集P = [1, 3, 5]和负子集N = [2, 4]

那么如何将其转换为子集求和问题:

                  sum(P) - sum(N) = target
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
                       2 * sum(P) = target + sum(nums)

因此,原来的问题已转化为一个求子集的和问题:
                    找到nums的一个子集 P,使得sum(P) = (target + sum(nums)) / 2

 上面的公式已经证明target + sum(nums)必须是偶数,否则输出为0

  1. 确定dp数组以及下标的含义
      dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
  2. 确定递推公式
    只要搞到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]] 累加起来。
    所以求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]]
  3. dp数组如何初始化
    初始化 dp[0] 为 1。
  4. 确定遍历顺序
    nums放在外循环,target在内循环,且内循环倒序。
  5. 举例推导dp数组
 int findTargetSumWays(vector<int>& nums, int target) 
      {
         int sum = accumulate(nums.begin(), nums.end(), 0);
         int aim = target + sum;
         int count = 0;

         if(aim % 2)  return count;
         if(abs(target) >sum)  return 0;

         aim = aim/2;

         vector<int>dp(aim+1,0);
         dp[0]=1;

         for(int i=0; i< nums.size();i++)
          for(int j=aim;j>=nums[i];j--)
          {
              dp[j] += dp[j-nums[i]];
          }

          return dp[aim];

      }

一和零:给定背包容量,装满背包最多有多少个物品。

题目链接:力扣

 看到有两个维度,一开始以为是多重背包,进入了误区

其实多重背包是每个物品数量不同的情况。

这道题本质上还是01背包问题,因为每一个字符串只取一次,而背包是两个维度的,即m和n 

  1. 确定dp数组(dp table)以及下标的含义
    dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
  2. 确定递推公式
    01背包的模板公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    设 strs里遍历到的字符串有zeroNum个0,oneNum个1。
    字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i]),也就是1
    所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
  3. dp数组如何初始化
    因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。
  4. 确定遍历顺序
    这里 物品就是strs里的字符串,背包容量就是题目描述中的m和n。
    所以先正序遍历strs,再分别倒叙遍历m和n
  5. 举例推导dp数组
class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
      
        vector<vector<int>> dp(m+1,vector(n+1,0));   //二维背包

        for(int i=0; i<strs.size(); i++)
        {
            int Num1=0;
            int Num0=0;

            for(int j=0;j<strs[i].size();j++)
            {
                if(strs[i][j] == '1')
                Num1++;
                else
                Num0++;
            }

            for(int a=m; a>=Num0;a--)
              for(int b=n; b>=Num1; b--)
            {
                dp[a][b] = max(dp[a][b],dp[a-Num0][b-Num1]+1);
            }
        }

        return dp[m][n];

    }
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值