问题描述
给定一个正数数组 arr,其中所有的值都为整数,以下是最小不可组成和的概念:
把 arr 每个子集内的所有元素加起来会出现很多值,其中最小的记为 min,最大的记为max 在区间[min,max]上,如果有数不可以被arr某一个子集相加得到,那么其中最小的那个数是arr 的最小不可组成和 在区间[min,max]上,如果所有的数都可以被arr的某一个子集相加得到,那么max+1是arr的最 小不可组成和。请写函数返回正数数组 arr 的最小不可组成和。
【举例】
arr=[3,2,5]。子集{2}相加产生 2 为 min,子集{3,2,5}相加产生 10 为 max。在区间[2,10] 上,4、 6 和 9 不能被任何子集相加得到,其中 4 是 arr 的最小不可组成和。
arr=[1,2,4]。子集{1}相加产生 1 为 min,子集{1,2,4}相加产生 7 为 max。在区间[1,7]上, 任何 数都可以被子集相加得到,所以 8 是 arr 的最小不可组成和。
思路
思路一: 使用暴力递归的方法进行解题。字符串arr每个下标i所对应的字符都有两种选择选与不选,因此我们可以计算出所有情况下的和,我们从最小的和开始遍历一次查找刚才结果中是否存在,最不存在直接返回结果,若遍历结束也没找到答案,则返回全部求和加一的结果。
思路二: 使用动态规划的思想解题。首先求出字符串中全部数据的求和记为sum,定义一个二维数组,行表示字符串arr的每一个字符,列表示0~sum。
我们知道二维数组的第一列全部为true,第一行的其他位置dp[0][arr[0]]为true,第一行其他位置全部为false。
二维数数组中间其他位置的判断标准为:dp[i][j] = dp[i-1][j] || ((j-arr[i]>=0)?dp[i-1][j-arr[i]]:false)
从最小的开始遍历,查找表格中第一个为false的位置,返回纵坐标的值。若遍历完二维表全部为true,则返回sum+1。
思路三: 该思路使用前提是arr数组中必须有1。对数组中的数字进行排序,i表示数组的小标,定义变量range表示下标i之前子字符串的和。分为两种情况:1.当前的值( arr[i] )大于range+1,此时直接返回range+1。2.当前的值( arr[i] )小于等于range+1,range在原来的基础上加上当前的值。
代码
思路一代码
public static int unformedSum1(int[] arr){
if (arr==null||arr.length==0){
return -1;
}
//收集所有min~max的能得到的累加和
Set<Integer> set = new HashSet<>();
process(arr,0,0,set);
int min = Integer.MAX_VALUE;
int sum = 0;
for (int i =0;i<arr.length;i++){
min = Math.min(min,arr[i]);
sum += arr[i];
}
for (int i = min+1;i<=sum;i++){
if (!set.contains(i)){
return i;
}
}
return sum+1;
}
private static void process(int[] arr,int index,int sum,Set<Integer> set){
//如果此时已经没有元素 可选
if (index==arr.length){
if (!set.contains(sum)){
set.add(sum);
}
return;
}
//要当前元素
process(arr,index+1,sum+arr[index],set);
//不要当前元素
process(arr,index+1,sum,set);
}
思路二代码
public static int unformedSum2(int[] arr){
if (arr==null||arr.length==0){
return -1;
}
int sum = 0;
int min = Integer.MAX_VALUE;
for (int i =0;i<arr.length;i++){
sum+=arr[i];
min = Math.min(min,arr[i]);
}
Set<Integer> set = new HashSet<>();
int cur = 0;
boolean[][] dp = new boolean[arr.length][sum+1];
for (int i =0;i<arr.length;i++){
dp[i][0] = true;
}
dp[0][arr[0]] = true;
for (int i =1;i<arr.length;i++){
for (int j =1;j<=sum;j++){
dp[i][j] = dp[i-1][j] ||((j-arr[i]>=0)?dp[i-1][j-arr[i]]:false);
}
}
for (int j =min;j<=sum;j++){
if (!dp[arr.length-1][j]){
return j;
}
}
return sum+1;
}
思路三代码
//已知arr中肯定有1这个数
public static int unformedSum3(int[] arr){
if (arr==null||arr.length==0){
return 0;
}
Arrays.sort(arr);//O(N*logN)
int range = 1;
for (int i=1;i!=arr.length;i++){
if (arr[i]>range+1){
return range+1;
}else {
range += arr[i];
}
}
return range+1;
}
测试代码
public static int[] generateArray(int len,int maxValue){
int[] res = new int[len];
for (int i =0;i<res.length-1;i++){
res[i] = (int)(Math.random()*maxValue)+1;
}
res[res.length-1] = 1;
return res;
}
public static int[] copyArray(int[] arr){
if (arr==null||arr.length==0){
return null;
}
int[] res = new int[arr.length];
for (int i = 0;i<arr.length;i++){
res[i] = arr[i];
}
return res;
}
public static void printArray(int[] arr){
for (int i=0;i!=arr.length;i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
int len = 10;
int maxValue = 30;
int testTime = 100000;
System.out.println("test begin!");
for (int i =0;i<testTime;i++){
int[] arr1 = generateArray(len,maxValue);
int[] arr2 = copyArray(arr1);
int[] arr3 = copyArray(arr1);
int res1 = unformedSum1(arr1);
int res2 = unformedSum2(arr2);
int res3 = unformedSum3(arr3);
if (res1 != res2 || res2 != res3){
System.out.println("test Ops!");
printArray(arr1);
printArray(arr2);
printArray(arr3);
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
break;
}
}
System.out.println("test end!");
}