問題描述:
給定一個只包含正整數的非空數組。是否可以將這個數組分割成兩個子集,使得兩個子集的元素和相等。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
大體思路:
由於想將數組分割爲兩個和相同的子集,因此可以先獲得數組整體的和,然後該問題可以轉化爲找數組中是否存在某幾個元素之和等於總和的一半。注:若總和爲奇數則可以直接返回false,由於數組全爲正整數,不會出現0.5的。
遞歸解法:
若給定一個函數dfs(i, target)表示從i位置開始能否組成target的值,對於每個i有選和不選兩種可能,想要返回true(構成target),要麼dfs(i + 1, target)返回true,要麼dfs(i + 1, target - nums[i])爲true。實現代碼入下:
public boolean dfs(int index, int[] nums, int target){
if(index == nums.length){
return target == 0;
}
return dfs(index + 1, nums, target) || dfs(index + 1, nums, target - nums[index]);
}
二維dp:
由於上述遞歸解法中存在大量的重複計算,而返回值只與index和target的取值有關,因此很容易想到改爲二維dp.該解法中dp[i][j] 表示從i位置開始能否構成j。
初值:dp[length][0] = true dp[length][j] = false j != 0
轉移函數:dp[i][j] = dp[i + 1][j] || dp[i + 1][j - nums[i]]
實現代碼如下:
public boolean canPartition(int[] nums) {
int sum = 0;
for(int num : nums){
sum += num;
}
if(sum % 2 != 0){
return false;
}
// 二維dp dp[i][j] 表示從i位置開始可以構成j
boolean[][] dp = new boolean[nums.length + 1][sum / 2 + 1];
dp[nums.length][0] = true;
for(int i = nums.length - 1; i >= 0; i--){
for(int j = 0; j < dp[0].length; j++){
if(dp[i + 1][j] || (j >= nums[i] && dp[i + 1][j - nums[i]])){
dp[i][j] = true;
}
}
}
return dp[0][sum / 2];
}
一維dp:
我們在二維dp中發現dp[i][ : ] 只與dp[i + 1][ : ]有關,因此很容易想到讓他們共用一個一維數組,即dp[j] 爲從當前位置開始能否構成j。此外我們發現dp[i][j] 的取值只有其正下方 和左下方的值有關,因此應該從右往左依次填表。(若從左至右填,會出現把第i1代當第i+1代)。
初值:dp[0] = true dp[j] = false j != 0
轉移函數:dp[j] = dp[j] || dp[j - nums[i]]
實現代碼如下:
public boolean canPartition(int[] nums) {
int sum = 0;
for(int num : nums){
sum += num;
}
if(sum % 2 != 0){
return false;
}
// 空間壓縮 一維dp[i]
boolean[] dp = new boolean[sum / 2 + 1];
dp[0] = true;
for(int i = nums.length - 1; i >= 0; i--){
for(int j = dp.length - 1; j >= 0; j--){
dp[j] = dp[j] || (j >= nums[i] && dp[j - nums[i]]);
}
}
return dp[sum / 2];
}