1. 程式人生 > 實用技巧 >【LeetCode-揹包】目標和

【LeetCode-揹包】目標和

題目描述

給定一個非負整數陣列,a1, a2, ..., an, 和一個目標數,S。現在你有兩個符號 + 和 -。對於陣列中的任意一個整數,你都可以從 + 或 -中選擇一個符號新增在前面。

返回可以使最終陣列和為目標數 S 的所有新增符號的方法數。

示例:

輸入:nums: [1, 1, 1, 1, 1], S: 3
輸出:5
解釋:

-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

一共有5種方法讓最終目標和為3。

說明:

  • 陣列非空,且長度不會超過 20 。
  • 初始的陣列的和不會超過 1000 。
  • 保證返回的最終結果能被 32 位整數存下。

題目連結: https://leetcode-cn.com/problems/target-sum/

思路

假設我們將陣列分為兩部分,兩部分的和分別為 x,y,陣列中所有元素的和為 sum,則有 x + y = sum, x - y = S,可得 x = (sum + S) / 2. 所以,問題就變為了陣列能否分為兩部分,其中一部分的和為 (sum + S) / 2。可以使用和等和子集一樣的方法來做。

  • 狀態定義:dp[i][j] 表示下標範圍為 [0, i] 內陣列元素中和為 j 的個數;
  • 狀態轉移:對於第 i 個數,可以選也可以不選,所以 dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
  • 邊界條件:dp[i][0] = 1,含義是範圍 [0, i] 內元素和為 0 的個數為 1,也就是所有的數都不選;dp[0][nums[0]] += 1 if nums[0]<=x,本來 dp[0][0] = 1,如果 nums[0] = 0,此時 dp[0][0] 就等於 2 了,也就可以選擇 nums[0],也可以不選擇 nums[0],因為 nums[0] = 0,所以這兩種方法是一樣的。

程式碼如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        if(nums.empty() && S>0) return 0;

        int s = 0;
        for(int i=0; i<nums.size(); i++){
            s += nums[i];
        }
        if(s<S) return 0;
        s += S;

        if(s%2!=0) return 0;
        int target = s / 2;

        vector<vector<int>> dp(nums.size(), vector<int>(target+1, 0));
        for(int i=0; i<nums.size(); i++) dp[i][0] = 1; // 邊界條件
        /*if(nums[0]<=target) dp[0][nums[0]] = 1;
        if(nums[0]==0) dp[0][nums[0]] = 2;*/
        if(nums[0]<=target) dp[0][nums[0]] += 1; // 這種寫法和上面註釋的寫法是一樣的

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

空間複雜度優化:
使用和等和子集一樣的方法對空間複雜度進行優化:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        if(nums.empty() && S>0) return 0;

        int s = 0;
        for(int i=0; i<nums.size(); i++){
            s += nums[i];
        }
        if(s<S) return 0;
        s += S;

        if(s%2!=0) return 0;
        int target = s / 2;

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