Bootstrap

代码随想录算法训练营day41|动态规划04

最后一块石头的重量||

返回剩余最后一块石头石头最小的可能重量,那么就应该最后剩余的两块石头尽量都等于或接近总重量的一半,这样剩下的就是一半的质量

目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

回溯的方法

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum == target) {
            result.push_back(path);
        }
        // 如果 sum + candidates[i] > target 就终止遍历
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i + 1);
            sum -= candidates[i];
            path.pop_back();

        }
    }
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (S > sum) return 0; // 此时没有方案
        if ((S + sum) % 2) return 0; // 此时没有方案,两个int相加的时候要格外小心数值溢出的问题
        int bagSize = (S + sum) / 2; // 转变为组合总和问题,bagsize就是要求的和

        // 以下为回溯法代码
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 需要排序
        backtracking(nums, bagSize, 0, 0);
        return result.size();
    }
};

解释:假设加法的总和为x,那么减法对应的总和就是sum - x。所以我们要求的是 x - (sum - x) = target,x = (target + sum) / 2,且若(target+sum)%2!=0的话,x就是无解的

本题要求的,就是装满j的背包有多少种方案,
不放物品i:即背包容量为j,里面不放物品i,装满有dp[i - 1][j]中方法。
放物品i: 即:先空出物品i的容量,背包容量为(j - 物品i容量),放满背包有 dp[i - 1][j - 物品i容量] 种方法

如何初始化
最左上角的元素,如果背包容量为0,就放0种物品,所以就是1种方法,
最左列也应该是为0,但如果存在元素为0的情况,则如下
在这里插入图片描述
二维数组的代码

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=0;
        for(int i=0;i<nums.size();i++) sum+=nums[i];
        if(abs(target)>sum) return 0;
        if((target+sum)%2==1) return 0;
        int bagSize=(target+sum)/2;
        vector<vector<int>> dp(nums.size(),vector<int>(bagSize+1,0));
        if(nums[0]<=bagSize) dp[0][nums[0]]=1;
        dp[0][0]=1;
        int numZero=0;
        for(int i=0;i<nums.size();i++){
            if(nums[i]==0) numZero++;
            dp[i][0]=(int) pow(2.0,numZero);//最左列向下递增
        }
        for(int i=1;i<nums.size();i++){
            for(int j=0;j<=bagSize;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]];
            }
        }
        return dp[nums.size()-1][bagSize];
    }
};

一维的方法初始化起来更简单

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (abs(target) > sum) return 0; // 此时没有方案
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        int bagSize = (target + sum) / 2;
        vector<int> dp(bagSize + 1, 0);
        dp[0] = 1;
        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];
    }

总结:在求装满背包有几种方法的情况下,递推公式一般为:dp[j]+=dp[j-nums[i]];

一和零

一道01背包问题,但这个背包有两个维度,一个是m,一个是n

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

注意!!!这里01背包都是从后往前遍历,因为是相当于两个一维动规的叠加

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 oneNum=0,zeroNum=0;
            for(char c:str){
                if(c=='0') zeroNum++;
                else oneNum++;
            }
            for(int i=m;i>=zeroNum;i--){
                //遍历两个背包且从后往前遍历
                for(int j=n;j>=oneNum;j--){
                    dp[i][j]=max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);
                }
        }
        
    }
    return dp[m][n];}
};
;