leetcode 416. 分割等和子集 中等 動態規劃

題目:
在這裏插入圖片描述
這裏我直接引用別人的題解,講動態規劃講的很好
動態規劃的一般思考方向
1、狀態定義;
2、狀態轉移方程;
3、初始化;
4、輸出;
5、思考狀態壓縮。
這 5 個部分是本題解的結構。其它類似的動態規劃問題也可以按照這樣的方向去思考、解釋和理解。

這是一個典型的“動態規劃”問題,並且它的“原形”是“0-1 揹包問題”。使用“動態規劃”解決問題的思路是“以空間換時間”,“規劃”這個詞在英文中就是“填表格”的意思,代碼執行的過程,也可以稱之爲“填表格”。

“動態規劃”的方法可以認爲是爲我們提供了一個思考問題的方向,我們不是直接面對問題求解,而是去找原始問題(或者說和原始問題相關的問題)的最開始的樣子,通過“狀態轉移方程”(這裏沒法再解釋了,可以結合下文理解)記錄下每一步求解的結果,直到最終問題解決。

而直接面對問題求解,就是我們熟悉的“遞歸”方法,由於有大量重複子問題,我們就需要加緩存,這叫“記憶化遞歸”,這裏就不給參考代碼了,感興趣的朋友可以自己寫一下,比較一下它們兩種思考方式的不同之處和優缺點。

做這道題需要做這樣一個等價轉換:是否可以從這個數組中挑選出一些正整數,使得這些數的和等於整個數組元素的和的一半。前提條件是:數組的和一定得是偶數,即數組的和一定得被 22 整除,這一點是特判。

本題與 0-1 揹包問題有一個很大的不同,即:

  • 0-1 揹包問題選取的物品的容積總量不能超過規定的總量;
  • 本題選取的數字之和需要恰恰好等於規定的和的一半。
    這一點區別,決定了在初始化的時候,所有的值應該初始化爲 false。 (《揹包九講》的作者在介紹 0-1 揹包問題的時候,有強調過這點區別,我在這裏也只是再重複一下。)

作爲“0-1 揹包問題”,它的特點是:“每個數只能用一次”。思路是:物品一個一個選,容量也一點一點放大考慮(這一點是“動態規劃”的思想,特別重要)。

如果在實際生活中,其實我們也是這樣做的,一個一個嘗試把候選物品放入“揹包”,看什麼時候能容納的價值最大。

具體做法是:畫一個 len 行,target + 1 列的表格。這裏 len 是物品的個數,target 是揹包的容量。len 行表示一個一個物品考慮,target + 1多出來的那 1 列,表示揹包容量從 0 開始,很多時候,我們需要考慮這個容量爲 0 的數值。

狀態定義:dp[i][j]表示從數組的 [0, i] 這個子區間內挑選一些正整數,每個數只能用一次,使得這些數的和恰好等於 j。
狀態轉移方程:很多時候,狀態轉移方程思考的角度是“分類討論”,對於“0-1 揹包問題”而言就是“當前考慮到的數字選與不選”。
1、不選擇 nums[i],如果在 [0, i - 1] 這個子區間內已經有一部分元素,使得它們的和爲 j ,那麼 dp[i][j] = true;

2、選擇 nums[i],如果在 [0, i - 1] 這個子區間內就得找到一部分元素,使得它們的和爲 j - nums[i]。

狀態轉移方程是:

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

一般寫出狀態轉移方程以後,就需要考慮邊界條件(一般而言也是初始化條件)。

1、j - nums[i] 作爲數組的下標,一定得保證大於等於 0 ,因此 nums[i] <= j;
2、注意到一種非常特殊的情況:j 恰好等於 nums[i],即單獨 nums[j] 這個數恰好等於此時“揹包的容積” j,這也是符合題意的。

因此完整的狀態轉移方程是:
在這裏插入圖片描述
初始化:dp[0][0] = false,因爲是正整數,當然湊不出和爲 0。
輸出:dp[len - 1][target],這裏 len 表示數組的長度,target 是數組的元素之和(必須是偶數)的一半。

代碼:

class Solution {
    public boolean canPartition(int[] nums) {
        if(nums == null || nums.length == 0){
            return false;
        }
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        //如果是奇數,直接返回false即可
        if (sum%2 != 0) {
            return false;
        }
        int target = sum / 2;
        //行:nums範圍,列:求解目標,目標也包括0
        boolean[][] dp = new boolean[nums.length][target + 1];

        // 第 1 個數只能恰是目標值纔有解
        if (nums[0] <= target) {
            dp[0][nums[0]] = true;
        }

        // 再求解之後的情況
        for (int i = 1; i < len; i++) {
            for (int j = 0; j <= target; j++) {
                if (nums[i] == j) {
                    dp[i][j] = true;
                    continue;
                }
                if (nums[i] < j) {
                    //如果nums[i] < j,那麼要麼前i-1範圍內能湊成target j,要麼前i-1範圍能湊成target j - nums[i]
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
                }
            }
        }
        return dp[len - 1][target];

    }
}

在這裏插入圖片描述
在這裏插入圖片描述
本文分析部分轉自鏈接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章