這道題主要是利用動態規劃進行求解,優化的時候可以找規律,轉化成正常的揹包問題。

原題

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

返回可以使最終數組和爲目標數 S 的所有添加符號的方法數。

示例 1:

輸入: 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。

注意:

  1. 數組非空,且長度不會超過20。

  2. 初始的數組的和不會超過1000。

  3. 保證返回的最終結果能被32位整數存下。

原題url:https://leetcode-cn.com/problems/target-sum/

解題

簡單遞歸

最簡單的方法就是計算出所有的可能性,如果最終結果等於目標值,則說明該情況可以。直接看一下代碼:

public class Solution {

    int result = 0;

    public int findTargetSumWays(int[] nums, int S) {
        // 遞歸
        findTargetSumWays(nums, S, 0, 0);
        // 返回最終結果
        return result;
    }

    // index : 當前計算到數組中的位置
    // sum : 當前的總和
    public void findTargetSumWays(int[] nums, int S, int index, int sum) {
        // 遍歷是否結束
        if (index == nums.length) {
            // 如果總和等於S,則最終結果+1
            if (sum == S) {
                result++;
            }
            return;
        }

        // 針對當前的數值,有加和減兩種情況
        findTargetSumWays(nums, S, index + 1, sum + nums[index]);
        findTargetSumWays(nums, S, index + 1, sum - nums[index]);
    }
}

方法很簡單,但是時間複雜度太高, O(2^n) ,執行用時在 java 中也只打敗了 31.65% ,看來確實不夠好。

簡單的動態規劃

這其實類似於 揹包問題 ,有容量要求(部分數字之和等於目標值)。首先我們來想一下狀態轉移方程:

我們用二維數組 dp[i][j] 表示用數組中的前 i 個元素,組成和爲 j 的方案數。考慮第 i 個數 nums[i] ,它可以被添加 + 或 -,因此狀態轉移方程如下:

dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]

也可以寫成遞推的形式:

dp[i][j + nums[i]] += dp[i - 1][j]
dp[i][j - nums[i]] += dp[i - 1][j]

因爲題目中提到所有數的和不超過 1000,那麼 j 的最小值可以達到 -1000。在 java 中,是不允許數組的下標爲負數的,因此我們需要給 dp[i][j] 的第二維預先增加 1000,那麼新的遞推關係如下:

dp[i][j + nums[i] + 1000] += dp[i - 1][j + 1000]
dp[i][j - nums[i] + 1000] += dp[i - 1][j + 1000]

接下來我們看看代碼:

public class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        if (S > 1000 || S < -1000) {
            return 0;
        }

        // 求和的最大值
        int max = 1000;
        // 初始的數組的和不會超過1000,因此最大爲1000,最小爲-1000
        int[][] dp = new int[nums.length][max * 2 + 1];
        // 針對nums[0],先求出第一步
        dp[0][nums[0] + max] = 1;
        dp[0][-nums[0] + max] += 1;
        // 遍歷數組
        for (int i = 1; i < nums.length; i++) {
            for (int sum = -max; sum <= max; sum++) {
                // 如果上一步有求出和爲sum,那麼可以繼續計算下一步
                if (dp[i - 1][sum + max] > 0) {
                    dp[i][nums[i] + sum + max] += dp[i - 1][sum + max];
                    dp[i][-nums[i] + sum + max] += dp[i - 1][sum + max];
                }
            }
        }

        return dp[nums.length - 1][S + max];
    }
}

提交OK,時間複雜度爲 O(N ∗ max) ,max 代表數組中所有數字之和的最大值,執行用時在 java 中打敗了 58.91% ,看來還有很大的提升空間。

動態規劃 + 找規律

我們想一下,之所以上面的方法會涉及到 max,因爲所謂的 求和 ,並不只是加法,也可以用減法。這和我們一般理解的 揹包問題 還是有所不同的,那麼我們是否可以將本題轉換成真正意義上的 揹包問題 呢?

首先,我們可以將這組數看成兩部分,P 和 N,其中 P 使用正號,N 使用負號,那麼可以推導出一下關係:

1、target = sum(P) - sum(N)
2、sum(nums) = sum(P) + sum(N)
由1可以得出:sum(P) = target + sum(N)
由2可以得出:sum(N) = sum(nums) - sum(P)
綜合以上,可以得出:
sum(P) = (target + sum(nums)) / 2

因此只要找到一個子集,令它們都取正號,並且和等於 (target + sum(nums))/2,就證明存在解,這就屬於正常的 0-1揹包問題 的範疇了。需要注意 target + sum(nums) 必須爲偶數,否則 sum(P) 就是小數了,這和題目要求的所有數都是 非負整數 不符。

接下來我們看看代碼:

public class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        if (S > 1000 || S < -1000) {
            return 0;
        }
        // 求出數組的和
        int sumNums = 0;
        for (int num : nums) {
            sumNums += num;
        }
        // 新的目標和(sumNums + S) / 2
        int newTarget = sumNums + S;
        // 如果是奇數,那麼無法被2整除,不符合規則
        if ((newTarget & 1) == 1) {
            return 0;
        }
        newTarget = newTarget / 2;

        // 正常的揹包問題,在nums中找幾個數,滿足和爲newTarget
        // dp[i]表示從數組nums找出和爲i的組合情況
        int[] dp = new int[newTarget + 1];
        dp[0] = 1;
        // 遍歷數組
        for (int num : nums) {
            // 更新dp
            for (int sum = newTarget; sum >= num; sum--) {
                dp[sum] += dp[sum - num];
            }
        }

        return dp[newTarget];
    }
}
提交OK,時間複雜度是`O(n * newTarget)`,其中,` newTarget = (target + sum(nums))/2`,和前面方法中的`max`相比,`sum(nums) <= max`,如果`target > max`,也會直接返回0,因此這個方法的時間複雜度更優。

## 總結

以上就是這道題目我的解答過程了,不知道大家是否理解了。這道題主要是利用 動態規劃 ,優化時可以 找規律 ,轉化成正常的 揹包問題

有興趣的話可以訪問我的博客或者關注我的公衆號、頭條號,說不定會有意外的驚喜。

https://death00.github.io/

公衆號:健程之道

點擊此處留言

相關文章