1. 程式人生 > >[LeetCode] Partition Equal Subset Sum 相同子集和分割

[LeetCode] 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陣列,要更新[nums[i], target]之間的值,那麼對於這個區間中的任意一個數字j,如果dp[j - nums[i]]為true的話,那麼dp[j]就一定為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];
    }
};

類似題目:

參考資料: