原文地址:Dynamic Programming | Set 18 (Partition problem)
分区问题是将已知的集合分成两个子集,这两个子集的元素分别加和是相等的。
例子:
arr[] = {1, 5, 11, 5}
Output: true
这个数组可以被分为:{1, 5, 5}和{11}
arr[] = {1, 5, 3}
Output: false
这个数组不能被分成两个和相同的子集.
根据下面的两个步骤来解决这个问题:
1、计算数组的所有元素的和,如果和是奇数,那么就不能分成两个和相等的子集,返回false。
2、如果所有元素的和是偶数,那就算一下sum/2,然后找出一个子集让这个子集的所有元素和等于sum/2。
第一步比较简单,第二步才是关键,第二步可以用递归或者动态规划来解决。
递归方法:
下面是上述第二步的递归属性。
设这个函数为isSubsetSum(arr, n, sum/2),如果arr[0..n-1]有一个子集的和等于sum/2。
isSubsetSum问题可以分成两个子问题:
a)isSubsetSum()不考虑最后一个元素(减少n到n-1);
b)isSubsetSum 考虑最后一个元素(sum/2减去arr[n-1],减少n到n-1)
如果以上两条任何一个子问题返回true,那么整个方法返回true。
isSubsetSum (arr, n, sum/2) = isSubsetSum (arr, n-1, sum/2) || isSubsetSum (arr, n-1, sum/2 - arr[n-1])
// A recursive Java solution for partition problem
import java.io.*;
class Partition
{
// A utility function that returns true if there is a
// subset of arr[] with sun equal to given sum
static boolean isSubsetSum (int arr[], int n, int sum)
{
// Base Cases
if (sum == 0)
return true;
if (n == 0 && sum != 0)
return false;
// If last element is greater than sum, then ignore it
if (arr[n-1] > sum)
return isSubsetSum (arr, n-1, sum);
/* else, check if sum can be obtained by any of
the following
(a) including the last element
(b) excluding the last element
*/
return isSubsetSum (arr, n-1, sum) ||
isSubsetSum (arr, n-1, sum-arr[n-1]);
}
// Returns true if arr[] can be partitioned in two
// subsets of equal sum, otherwise false
static boolean findPartition (int arr[], int n)
{
// Calculate sum of the elements in array
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
// If sum is odd, there cannot be two subsets
// with equal sum
if (sum%2 != 0)
return false;
// Find if there is subset with sum equal to half
// of total sum
return isSubsetSum (arr, n, sum/2);
}
/*Driver function to check for above function*/
public static void main (String[] args)
{
int arr[] = {3, 1, 5, 9, 12};
int n = arr.length;
if (findPartition(arr, n) == true)
System.out.println("Can be divided into two "+
"subsets of equal sum");
else
System.out.println("Can not be divided into " +
"two subsets of equal sum");
}
}
/* This code is contributed by Devesh Agrawal */
输出:
Can be divided into two subsets of equal sum
最坏情况下的时间复杂度是:O(2^n) ,这种解决方法对于每一个元素都尝试了两种可能(是否包含)。
动态规划方法
当数组中元素的和太大的时候,这个问题就可以用动态规划的方法来解决了。我们可以建立一个大小为(sum/2)*(n+1)的二维数组part[][]。我们可以用自底向上的方法构建这个方法,这样的话每一个entry都有符合下面的式子:
part[i][j] = true if a subset of {arr[0], arr[1], ..arr[j-1]} has sum equal to i, otherwise false
// A dynamic programming based Java program for partition problem
import java.io.*;
class Partition {
// Returns true if arr[] can be partitioned in two subsets of
// equal sum, otherwise false
static boolean findPartition (int arr[], int n)
{
int sum = 0;
int i, j;
// Caculcate sun of all elements
for (i = 0; i < n; i++)
sum += arr[i];
if (sum%2 != 0)
return false;
boolean part[][]=new boolean[sum/2+1][n+1];
// initialize top row as true
for (i = 0; i <= n; i++)
part[0][i] = true;
// initialize leftmost column, except part[0][0], as 0
for (i = 1; i <= sum/2; i++)
part[i][0] = false;
// Fill the partition table in botton up manner
for (i = 1; i <= sum/2; i++)
{
for (j = 1; j <= n; j++)
{
part[i][j] = part[i][j-1];
if (i >= arr[j-1])
part[i][j] = part[i][j] ||
part[i - arr[j-1]][j-1];
}
}
/* // uncomment this part to print table
for (i = 0; i <= sum/2; i++)
{
for (j = 0; j <= n; j++)
printf ("%4d", part[i][j]);
printf("\n");
} */
return part[sum/2][n];
}
/*Driver function to check for above function*/
public static void main (String[] args)
{
int arr[] = {3, 1, 1, 2, 2,1};
int n = arr.length;
if (findPartition(arr, n) == true)
System.out.println("Can be divided into two "
"subsets of equal sum");
else
System.out.println("Can not be divided into"
" two subsets of equal sum");
}
}
/* This code is contributed by Devesh Agrawal */
输出:
Can be divided into two subsets of equal sum
下图显示了值所在的分区表,这个图来自wiki page of partition problem
时间复杂度是:O(sum*n)
空间复杂度是:O(sum*n)
注意:如果数组的元素的和太大的话,这个方法就不再适用了。