例如:
arr[] = {1, 5, 11, 5}
Output: true
這個數組可以劃分爲: {1, 5, 5} 和 {11}
arr[] = {1, 5, 3}
Output: false
無法劃分爲總和相等的兩部分
如果劃分後的兩個集合總和相等,則原集合的總和肯定爲偶數,假設爲總和爲sum。問題即爲是否有子集合的總和爲sum/2.
遞歸解決
設函數 isSubsetSum(arr, n, sum/2) 返回true如果存在arr的一個子集合的總和爲 sum/2,isSubsetSum函數爲分爲下面兩個子問題
1) 不考慮最後一個元素。問題遞歸到 isSubsetSum(arr, n-1. sum/2)
2) 考慮最後一個元素。問題遞歸到 isSubsetSum(arr, n-1. sum/2-arr[n])
上面兩種情況有一個返回TRUE即可
isSubsetSum (arr, n, sum/2) = isSubsetSum (arr, n-1, sum/2) ||
isSubsetSum (arr, n-1, sum/2 – arr[n-1])
具體實例及實現代碼如下所示:
/** * @Title: Partition.java * @Package dynamicprogramming * @Description: TODO * @author peidong * @date 2017-6-12 上午9:12:18 * @version V1.0 */ package dynamicprogramming; /** * @ClassName: Partition * @Description: 劃分問題 * @date 2017-6-12 上午9:12:18 * */ public class Partition { /** * * @Title: isSubsetSum * @Description: 判斷數組是否可劃分 * @param arr 數組 * @param n 數組長度 * @param sum 數組和 * @return * @return boolean * @throws */ public static boolean isSubsetSum(int[] arr, int n, int sum){ //邊界條件判斷 if(sum == 0) return true; if( n == 0 && sum!= 0) return false; //如果最後一個元素比sum大,就不考慮該元素 if(arr[n-1] > sum) return isSubsetSum(arr, n-1, sum); //分別判斷包括最後一個元素和不包括最後一個元素 return isSubsetSum(arr, n-1, sum) || isSubsetSum(arr, n-1, sum - arr[n-1]); } /** * * @Title: findPartitionRecursion * @Description: 利用遞歸求解數組劃分問題 * @param arr 數組名 * @param n 數組長度 * @return * @return boolean * @throws */ public static boolean findPartitionRecursion(int[] arr, int n){ int sum = 0; for(int i = 0; i < n; i++){ sum += arr[i]; } //sum爲奇數 if(sum%2 != 0) return false; return isSubsetSum(arr, n, sum/2); } //時間複雜度:最快情況爲 O(2^n),即每個元素有選或不選的兩種選擇 public static boolean findPartition(int[] arr, int n){ int sum = 0; for(int i = 0; i < n; i++){ sum += arr[i]; } if(sum %2 != 0) return false; //創建狀態轉移矩陣 boolean[][] tc = new boolean[sum/2 + 1][n+1]; //初始化狀態轉移矩陣 for(int i = 0; i <= n; i++){ tc[0][i] = true; } for(int i = 1; i <= sum/2; i++){ tc[i][0] = false; } //構建狀態轉移矩陣 for(int i = 1; i <= sum/2; i++){ for(int j = 1; j <=n; j++){ tc[i][j] = tc[i][j-1]; if(i >= arr[j-1]) tc[i][j] = tc[i][j] || tc[i-arr[j-1]][j-1]; } } //打印狀態轉移矩陣 for(int i = 0; i < sum/2; i++){ for(int j = 0; j <= n; j++){ System.out.print(tc[i][j] + " "); } System.out.println(); } return tc[sum/2][n]; } /** * @Title: main * @Description: 測試用例 * @param args * @return void * @throws */ public static void main(String[] args) { // TODO Auto-generated method stub int[] arr = {3, 1, 5, 9, 10}; int n = arr.length; if(findPartitionRecursion(arr, n) == true) System.out.println("數組能夠被劃分"); else System.out.println("數組不能被劃分"); if(findPartition(arr, n) == true) System.out.println("dp數組能夠被劃分"); else System.out.println("dp數組不能被劃分"); } } //時間複雜度爲 O(sum*n)