程式碼隨想錄演算法訓練營第四十三天|● 1049. 最後一塊石頭的重量 II ● 494. 目標和 ● 474.一和零

SandaiYoung發表於2024-03-11

最後一塊石頭的重量 II

題目連結:1049. 最後一塊石頭的重量 II - 力扣(LeetCode)

思路:儘可能將石頭分成重量相近的兩堆,結果一定最小,因此問題可以轉換為分割子集。dp[i]的含義是揹包容量為i的揹包能裝下的最大重量,由於題目中最大重量是15000,所以我們申請15001的vector。

注意,結果不是target-dp[target],儘管和sum-2*dp[target]十分相似,但是結果還是差了1。

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        vector<int> dp(15001,0);
        int sum=0;
        for(auto i:stones)sum+=i;
        int target=sum/2;
        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-2*dp[target];
    }
};

目標和

題目連結:494. 目標和 - 力扣(LeetCode)

思路:乍一看,確實很難把他當做一個動態規劃問題。這裡有一個公式推導:本題要如何使表示式結果為target,既然為target,那麼就一定有 left組合 - right組合 = target。left + right = sum,而sum是固定的。right = sum - left。公式來了, left - (sum - left) = target 推匯出 left = (target + sum)/2 。target是固定的,sum是固定的,left就可以求出來。

此時問題就是在集合nums中找出和為left的組合。而且要注意,(target + sum)/2向下取整也是有影響的,比如target=2,sum=5,這樣是無解的。同樣如果target>sum,也是無解的。

本題特殊之處在於之前的揹包問題都是求揹包最多裝多少東西,本題求裝滿有幾種裝法,是組合問題,我實在是想不出如何dp,投降....

dp的產生:有哪些來源可以推出dp[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]] 累加起來。同時也要關注dp[0]的初始化問題。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=0;
        for(auto i:nums)sum+=i;
        if(sum<abs(target))return 0;
        if((sum+target)&1)return 0;

        int size=(sum+target)/2;
        vector<int> dp(size+1,0);
        dp[0]=1;
        for(int i=0;i<nums.size();i++){
            for(int j=size;j>=nums[i];j--){
                dp[j]+=dp[j-nums[i]];
            }
        }
        return dp[size];
    }
};

dp[j]+=dp[j-nums[i]];這個公式很重要,請記住。

一和零

題目連結:474. 一和零 - 力扣(LeetCode)

思路:儘管這裡有兩個緯度的“重量”,但是該問題依然是一個01揹包問題,因為每一個物品只能取一次。

這裡我們定義一個二維dp陣列,dp[i][j]表示i個0,j個1最多能有多大的子集。先用count陣列統計每個子字串裡有多少個0和1,然後用一個三重for迴圈計算dp。

注意,二維陣列是vector<vector<int>>不是vector<int,vector<int>>,哎老糊塗了。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        vector<vector<int>> count(strs.size(),vector<int>(2,0));

        for(int i=0;i<strs.size();i++){
            for(char a:strs[i]){
                if(a =='0') count[i][0]+=1;
                else count[i][1]+=1;
            }
        }

        for(int i=0;i<strs.size();i++){
            for(int j=m;j>=count[i][0];j--){
                for(int k=n;k>=count[i][1];k--){
                    dp[j][k]=max(dp[j][k],dp[j-count[i][0]][k-count[i][1]]+1);
                }
            }
        }
        return dp[m][n];
    }
};

相關文章