【C++编程能力提升】

代码随想录训练营Day43 | Leetcode 1049、494、474

  • 一、1049 最后一块石头的重量II
  • 二、494 目标和
  • 三、474 一和零

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

题目链接:1049 最后一块石头的重量II

核心:建模成01背包问题,所有石头两两相减,要求最后剩下的重量最小,也就是将所有石头尽可能“等分”成2组,如果完全等分那么剩下重量为0,是理想情况;如果不能恰好等分就分成一组为sum/2(等分即整除情况,非整除情况取其小值,另一组为sum-sum/2)。因此背包最大容量是sum/2,物品价值即石头重量。
dp[j]:背包容量j能包含的石头的最大重量,dp[sum/2]==sum/2说明恰好等分。

    int lastStoneWeightII(vector<int>& stones) {
//两两相减最终得到最小的差值,实质是将所有元素划分成两组子集,且子集之和差值最小,即2组子集之和尽可能相等,与分割等和子集问题类似
        vector<int> dp(15001,0);    //dp[i]:容量i的背包所拥有的最大重量
        int sum=0;  //记录所有元素之和
        for(int stone:stones)
            sum+=stone;
        int target=sum/2;   //最佳情况是子集差值为0,即每个子集之和是总和的一半
        for(int i=0;i<stones.size();++i)
        {//先遍历物品,即依次将各个物品放入背包,确定不同容量j的背包的最大重量
            for(int j=target;j>=stones[i];--j)  //再遍历背包容量,容量小于物品重量则无法放置
                dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
        }//dp[target]表示背包容量是target,所包含石头元素的最大重量,如果等于target说明恰好等分
        return sum-dp[target]-dp[target];   //dp[target]是“等分”子集之和,sum-dp[target]是另一个子集之和
    }

二、494 目标和

题目链接:494 目标和

核心:建模成01背包问题,要求所有元素之和(包括差)为target,即两组子集之差为target:left-right=target,且这两组子集之和为所有元素之和sum。因此我们要求解的target的方法数就转换成子集之和为left的不同方法数。
综上,背包最大容量为left=(sum+target)/2;物品即数组内各元素;
dp[j]:背包容量为j(子集之和为j)时,不同的组合数;
递推公式:涉及到组合问题,一般是累加,比如:dp[3]的组合数包括,第一,给定元素1,那么dp[2]是其中一种组合方法;第二,给定元素2,dp[1]是其中一种组合方法;第三,给定元素3,dp[0]是其中一种组合方法。以上三种都是dp[3]的组合方法,因此需要累加上述不同的组合。

    int findTargetSumWays(vector<int>& nums, int target) {
//转换成两组子集(left、right)之差为target,之和为sum,那么子集left的不同构造数也就是元素和为target的不同方法数
        int sum=0;
        for(int num:nums)
            sum+=num;   //元素之和
        if(abs(target)>sum)
            return 0;
        if((target+sum)%2==1)
            return 0; //无法整除说明left是近似值,此时right=sum-left无法满足left-right=target
        int left=(target+sum)/2;    //背包最大容量——子集left之和
        vector<int> dp(left+1,0);   //dp[i]:装满容量为i的背包有dp[i]种不同方法
        dp[0]=1;    //初始化,容量为0的背包有1种方法
        for(int i=0;i<nums.size();++i)
        {//先遍历物品,即数组各元素;再遍历背包容量j
            for(int j=left;j>=nums[i];--j)
                dp[j]+=dp[j-nums[i]];  //递推公式:比如容量j的组合方法是累加的,组合通常都是累加
        }
        return dp[left];
    }

三、474 一和零

题目链接:474 一和零

核心:建模成01背包问题,本题的m和n都是背包的容量,是2个不同维度的容量,而物品是字符串数组中的字符串。

   int findMaxForm(vector<string>& strs, int m, int n) {
    //背包容量有2个维度m和n,即给定背包容量使得装满背包最多可有多少物品(子集)
    //dp[i][j]:背包最多有i个0和j个1,最多可包含的子集个数
    //递推公式:由上一个子集可推导到当前子集,且保留最大值
        vector<vector<int>> dp(m+1,vector<int> (n+1,0)); 
        //dp[0][0]=0; //初始化为0
        for(string str:strs)
        {//遍历字符串数组,即子集(物品)
            int zeronum=0;
            int onenum=0;
            for(char ch:str)
            {//遍历某个字符串各字符,统计0和1的个数
                if(ch=='0')
                    zeronum++;
                else
                    onenum++;
            }
            for(int i=m;i>=zeronum;--i)
            {//遍历背包容量第一个维度:0的个数,依然是倒序遍历
                for(int j=n;j>=onenum;--j)
                {//遍历背包容量第二个维度:1的个数
                    dp[i][j]=max(dp[i][j],dp[i-zeronum][j-onenum]+1);
                }
            }
        }
        return dp[m][n];    //返回背包容量最多有m个0和n个1可包含子集的最大数目
    }

你可能感兴趣的:(C++编程,c++,leetcode,动态规划)