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/