正数数组的最小不可组成和

题目

给定一个正整数数组arr,其中所有的值都为整数,以下是最小不接组成和的概念

  • 把arr每个子集的所有元素加起来出现很多值,其中最小的记为min,最大的记为max
  • 在区间[min,max]上,如果有数不可以被arr某个子集相加得到,那么其中最小的那个数就是最小不可组成和。
  • 如果[min,max]所有的数都可以相加得到,那么max+1就是arr的最小不可组成和

举例

arr=[1,2,3,4],返回11
arr=[2,3,4] 返回7

解答

暴力搜索

  • 收集每一个子集的累加和,存到一个hash表中,然后从min值开始递增查找,哪个正数不再hash表中,即返回该数即可
public int baoli(int[]arr){
    if(arr==null||arr.length==0)
        return 1;
    HashSet<Integer> set=new HashSet<>();
    process(arr,0,0,set);
    int min=Integer.MAX_VALUE;
    for(int i=0;i!=arr.length;i++){
        min=Math.min(min,arr[i]);
    }
    for(int i=min+1;i!=Integer.MAX_VALUE ;i++){
        if(!set.contains(i))
            return i;
    }
    return 0;
}
public void process(int[]arr,int i,int sum;HashSet <Integer>set){
if(i==arr.length){
    set.add(sum);
    return ;
}
    process(arr,i+1,sum,set);//不包含当前数arr[i]
    process(arr,i+1,sum+arr[i],set);//包含当前数
}

分析

  • 子集的个数为2^N,时间复杂度为O(2^N),收集子集和的过程,递归函数process最多有N层,空间复杂度O(N)

动态规划

思路分析
  • 假设arr所有数累加和为sum,那么arr子集的累加和必定在[0,sum]区间内,可以申请空间sum+1的boolean型数组dp[],dp[i]如果为true,那么表示i可以被arr的子集相加得到,
  • 如果arr[0…i]这个范围的数组成的子集可以累加出k,那么arr[0…i+1]组成的子集必然也可以累加出k+arr[i+1]
代码
public int dymicp(int[]arr){
    if(arr==null||arr.length==0)
        reeturn 1;
    int sum=0,min=Math.MAX_VALUE;
    for(int i=0;i<arr.length;i++)
    {
        sum+=arr[i];
        min=Math.min(arr[i],min);
    }
    boolean[] dp=new boolean[sum+1];
    dp[0]=true;
    for(int i=0;i!=arr.length;i++){
        for(int j=sum;j>=arr[i];j--){
            dp[j]=dp[j-arr[i]]?true:dp[j];
        }
    }
    for(int i=min;i!=dp.length;i++){
        if(!dp[i])
            return i;
    }
    return sum+1;

}

复杂度

  • 时间复杂度:0~sum的累加和都要看是否能被夹出来,所以每次更新的时间复杂度都是O(sum),子集和状态从arr[0]的范围增加到arr[0…N-1],所以更新的次数为N,总体的时间复杂度为O(N*sum)
  • 空间复杂度为:O(N)

特殊情况,正数数组中含有1

分析
  1. 将arr排序,必然有arr[0]=1;
  2. 从左往右计算每个位置i上的range(0<=i<=N).range代表计算到arr[i]时,[1,range]区间内得所有正数都可以被arr[0…i-1]的某个子集相加表示,初始时arr[0],range=0;
  3. 如果arr[i]>range+1,因为arr是有序的,所以arr[i]往后的数都不是出现range+1,直接返回;如果arr[i]<=range+1,说明[1,range+arr[i]]区间内的所有整数都可以被arr[0,…i-1]的某个子集表示,这是因为区间[1,range],可以由[0..i-1]子集和表示,对于k>range,的数,k-arr[i]
实现
public int fin(int []arr){
    if(arr==null||arr.length==0)
        return 1;
    Arrays.sort(arr);
    int range=0
    for(int i=0;i<arr.length;i++){
        if(arr[i]>range+1)
            return range+1;
        else
            range+=arr[i];
    }
    return range+1;
}
### **问题分析** 当数组中存在**最小元素小于 \(-S\)**(\(S\) 为数组)时,动态规划的子集范围需要重新评估。例如: ```cpp nums = [-5, 3, 2]; // 最小元素为-5,总S=0 ``` 此时: - 理论子集范围:\([-5, 5]\)(选 \(-5\) 或全选正数 \(3+2=5\))。 - 但若错误地以 \(S=0\) 为中心定义范围(如 \([-S, S] = [0, 0]\)),会遗漏可能的。 --- ### **关键观察** 1. **子集最小值**: - 最坏情况下,子集最小值为 **数组最小元素**(如单独选该元素)。 - 最大值为 **数组所有正数**(全选正数)。 2. **总 \(S\) 的误导性**: - \(S\) 是所有元素的,但子集的范围可能远超 \([-|S|, |S|]\)。 - 例如:`[-100, 1, 1]` 的 \(S=-98\),但子集范围是 \([-100, 2]\)。 --- ### **解决方案** #### **1. 动态计算子集的范围** 无需依赖 \(S\),直接遍历数组计算: - **最小**:数组最小元素(或部分负)。 - **最大**:所有正数。 ```cpp int min_sum = *min_element(nums.begin(), nums.end()); int max_sum = accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + max(b, 0); }); // 仅累加正数 ``` #### **2. 调整动态规划的范围** 基于 `min_sum` `max_sum` 定义偏移量: ```cpp int offset = -min_sum; // 将最小映射到0 int max_j = max_sum + offset; // 最大索引 vector<bool> dp(max_j + 1, false); dp[offset] = true; // 初始为0 ``` #### **3. 完整代码实现** ```cpp #include <vector> #include <algorithm> #include <numeric> #include <climits> using namespace std; bool canSumToTargetWithExtremes(const vector<int>& nums, int target = 0) { // 计算子集最小最大值 int min_sum = *min_element(nums.begin(), nums.end()); int max_sum = accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + max(b, 0); }); // 调整target到有效范围 if (target < min_sum || target > max_sum) return false; // 动态规划 int offset = -min_sum; int max_j = max_sum + offset; vector<bool> dp(max_j + 1, false); dp[offset] = true; // 为0可行 for (int num : nums) { vector<bool> new_dp = dp; for (int j = 0; j <= max_j; j++) { if (dp[j]) { int new_j = (j - offset) + num; if (new_j >= min_sum && new_j <= max_sum) { int new_k = new_j + offset; if (new_k >= 0 && new_k <= max_j) { new_dp[new_k] = true; } } } } dp = move(new_dp); if (dp[target + offset]) return true; // 提前终止 } return dp[target + offset]; } ``` --- ### **边界情况处理** 1. **目标超出范围**: - 若 `target < min_sum` 或 `target > max_sum`,直接返回 `false`。 2. **空数组**: - 仅当 `target == 0` 时返回 `true`。 3. **所有元素为负**: - 如 `nums = [-3, -2]`,`max_sum = 0`(无正数),子集范围是 \([-3, 0]\)。 --- ### **优化与变种** #### **1. 哈希表法(无需预计算范围)** ```cpp bool canSumToTargetHash(const vector<int>& nums, int target = 0) { unordered_set<int> dp = {0}; for (int num : nums) { unordered_set<int> new_dp = dp; for (int sum : dp) { new_dp.insert(sum + num); } dp = move(new_dp); if (dp.count(target)) return true; } return dp.count(target); } ``` - **优点**:自动处理任意范围的。 - **缺点**:空间复杂度较高。 #### **2. 统计方案** 将 `bool` 改为 `int` 计: ```cpp vector<int> dp(max_j + 1, 0); dp[offset] = 1; // 初始为0的方案为1 // 更新逻辑类似,改为 += ``` --- ### **复杂度分析** - **时间复杂度**:\(O(n \cdot \text{range})\),其中 `range = max_sum - min_sum`。 - **空间复杂度**: - 动态规划表:\(O(\text{range})\)。 - 哈希表法:最坏 \(O(2^n)\)(所有子集唯一时)。 --- ### **总结** - **核心问题**:总 \(S\) 不能直接界定子集范围,需单独计算 `min_sum` `max_sum`。 - **关键步骤**: 1. 计算子集的实际范围(`min_sum` 到 `max_sum`)。 2. 通过偏移量 `offset = -min_sum` 映射到非负索引。 3. 动态规划或哈希表求解。 - **推荐方法**:哈希表法更通用,动态规划表在范围可控时更高效。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值