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:
Each of the array element will not exceed 100.
The array size will not exceed 200.
題意:給定一個非空非負的數組,判斷其能否分成元素和大小相等的兩個子集。
分析: 那我第一個想法就是遞歸搜索所有情況,一個元素要麼在sub1 要麼在sub2,即遍歷一次數組,對每個元素採取 “取”或“不取”兩種操作,遍歷完後 判斷sub1的和是否等於sub2的和。
暴力遞歸:超時
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0)
return true;
int sum = 0;
for (int x : nums)
sum += x;
return dfs(nums,0,0,sum);
}
public boolean dfs(int[] nums,int start , int left,int right)
{
if(start == nums.length || left == right)
{
if(left == right)
return true;
else
return false;
}
return dfs(nums,start+1,left+nums[start],right-nums[start])||dfs(nums,start+1,left,right);
}
如何優化,是否可以記憶化:
其實已經想到這個問題,可以轉化爲判斷是否存在一個子集,它的和爲一個目標值(在這裏爲totalSum/2),這樣一但總和爲奇數也可直接判斷。
那對於這樣一個問題,感覺和Sum2以及Sum3很像。但是不一樣,Sum2指定了只能選擇兩個數達到target,而這裏可以遍歷整個數組,對每個數有要 和 不要 兩種選擇,說到這裏是否感覺和01揹包問題很像。
對了,這道題的實戰就是一道01揹包問題,正好題目也限制了元素的個數和大小,不至於過大!!!
那01揹包是最基礎的一道DP問題,給定揹包的容量,讓我們從物品中選出價值最大的組合。
而這裏,問題實際上就是給定一個容量,讓我們判斷是否有一種組合剛好放滿揹包。
狀態定義:dp[i][j]表示前i個物品是否存在子集和爲j
狀態轉移方程:dp[i][j] = dp[i-1][j] || dp[i-1][j-v[i]]
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0)
return true;
int sum = 0;
for (int x : nums)
sum += x;
if (sum % 2 == 1)
return false;
int tar = sum / 2;
boolean[][] dp = new boolean[nums.length+1][tar+1];
dp[0][0] = true;
for (int i = 1 ; i < dp.length ; i++)
dp[i][0] = nums[i-1]==0 && dp[i-1][0];
for (int i = 1 ; i < dp.length ; i ++)
{
for (int j = 1; j <= tar ; j++)
{
if (nums[i-1] <= j)
dp[i][j] = dp[i-1][j-nums[i-1]] || dp[i-1][j];
else
dp[i][j] = dp[i-1][j];
}
}
return dp[nums.length][tar];
}
01揹包還可以進行空間優化,一維數組,從後往前遍歷。
//一維揹包,此時當前dp[j]表示對於前i個物品容量能否達到j
//之所以從後往前,是因爲下一層需要用到dp[j-nums[i]],避免被更新
public boolean betterdp_canPartition(int[] nums) {
if(nums == null || nums.length == 0)
return true;
int sum = 0;
for (int x : nums)
sum += x;
if (sum % 2 == 1)
return false;
int tar = sum / 2;
boolean[] dp = new boolean[tar +1];
dp[0] = true;
for (int i = 1 ; i < nums.length ; i ++)
{
for (int j = tar; j >= 0 ; j--)
{
if (nums[i-1] <= j)
dp[j] = dp[j-nums[i-1]] || dp[j];
}
}
return dp[tar];
}