[LeetCode] 416. Partition Equal Subset Sum 相同子集和分割


 

Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.

Note:
Both the array size and each of the array element will not exceed 100.

Example 1:

Input: [1, 5, 11, 5]

Output: true

Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

Input: [1, 2, 3, 5]

Output: false

Explanation: The array cannot be partitioned into equal sum subsets.

 

這道題給了我們一個數組,問這個數組能不能分成兩個非空子集合,使得兩個子集合的元素之和相同。那么想,原數組所有數字和一定是偶數,不然根本無法拆成兩個和相同的子集合,只需要算出原數組的數字之和,然后除以2,就是 target,那么問題就轉換為能不能找到一個非空子集合,使得其數字之和為 target。開始博主想的是遍歷所有子集合,算和,但是這種方法無法通過 OJ 的大數據集合。於是乎,動態規划 Dynamic Programming 就是不二之選。定義一個一維的 dp 數組,其中 dp[i] 表示原數組是否可以取出若干個數字,其和為i。那么最后只需要返回 dp[target] 就行了。初始化 dp[0] 為 true,由於題目中限制了所有數字為正數,就不用擔心會出現和為0或者負數的情況。關鍵問題就是要找出狀態轉移方程了,需要遍歷原數組中的數字,對於遍歷到的每個數字 nums[i],需要更新 dp 數組,既然最終目標是想知道 dp[target] 的 boolean 值,就要想辦法用數組中的數字去湊出 target,因為都是正數,所以只會越加越大,加上 nums[i] 就有可能會組成區間 [nums[i], target] 中的某個值,那么對於這個區間中的任意一個數字j,如果 dp[j - nums[i]] 為 true 的話,說明現在已經可以組成 j-nums[i] 這個數字了,再加上 nums[i],就可以組成數字j了,那么 dp[j] 就一定為 true。如果之前 dp[j] 已經為 true 了,當然還要保持 true,所以還要 ‘或’ 上自身,於是狀態轉移方程如下:

dp[j] = dp[j] || dp[j - nums[i]]         (nums[i] <= j <= target)

有了狀態轉移方程,就可以寫出代碼了,這里需要特別注意的是,第二個 for 循環一定要從 target 遍歷到 nums[i],而不能反過來,想想為什么呢?因為如果從 nums[i] 遍歷到 target 的話,假如 nums[i]=1 的話,那么 [1, target] 中所有的 dp 值都是 true,因為 dp[0] 是 true,dp[1] 會或上 dp[0],為 true,dp[2] 會或上 dp[1],為 true,依此類推,完全使的 dp 數組失效了,參見代碼如下:

 

解法一:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0), target = sum >> 1;
        if (sum & 1) return false;
        vector<bool> dp(target + 1, false);
        dp[0] = true;
        for (int num : nums) {
            for (int i = target; i >= num; --i) {
                dp[i] = dp[i] || dp[i - num];
            }
        }
        return dp[target];
    }
};

 

這道題還可以用 bitset 來做,感覺也十分的巧妙,bisets 的大小設為 5001,為啥呢,因為題目中說了數組的長度和每個數字的大小都不會超過 100,那么最大的和為 10000,那么一半就是 5000,前面再加上個0,就是 5001 了。初始化把最低位賦值為1,算出數組之和,然后遍歷數字,對於遍歷到的數字 num,把 bits 向左平移 num 位,然后再或上原來的 bits,這樣所有的可能出現的和位置上都為1。舉個例子來說吧,比如對於數組 [2,3] 來說,初始化 bits 為1,然后對於數字2,bits 變為 101,可以看出來 bits[2] 標記為了1,然后遍歷到3,bits 變為了 101101,看到 bits[5],bits[3],bits[2] 都分別為1了,正好代表了可能的和 2,3,5,這樣遍歷完整個數組后,去看 bits[sum >> 1] 是否為1即可,參見代碼如下:

 

解法二:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        bitset<5001> bits(1);
        int sum = accumulate(nums.begin(), nums.end(), 0);
        for (int num : nums) bits |= bits << num;
        return (sum % 2 == 0) && bits[sum >> 1];
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/416

 

類似題目:

Partition to K Equal Sum Subset

 

參考資料:

https://leetcode.com/problems/partition-equal-subset-sum/

https://leetcode.com/problems/partition-equal-subset-sum/discuss/90592/01-knapsack-detailed-explanation

https://leetcode.com/problems/partition-equal-subset-sum/discuss/90590/Simple-C++-4-line-solution-using-a-bitset

https://leetcode.com/problems/partition-equal-subset-sum/discuss/90588/Concise-C++-Solution-summary-with-DFS-DP-BIT

https://leetcode.com/problems/partition-equal-subset-sum/discuss/90627/Java-Solution-similar-to-backpack-problem-Easy-to-understand

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM