【算法】【算法杂谈】正数数组的最小不可组成和

文章介绍了如何使用暴力递归和动态规划两种方法解决最小不可组成和的问题,并提供了Java代码实现。进阶问题中,当数组包含1时,提出了更快的解决方案。作者强调了理解递归到动态规划转换的重要性,并讨论了不同解法的空间效率。此外,还鼓励读者对代码进行学习和批判性思考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~

在此感谢左大神让我对算法有了新的感悟认识!

问题介绍

原问题
最小不可组成和概念:给定一个正数数组arr,其中arr中的任意几个数的和记录为sum,则sum可能的范围就是
[min…max],其中min为arr中最小的数,max为arr中所有数的和,其中在所有和中,没有出现的最小的数就是最小不可组成和。
问题:给定正数数组,求最小不可组成和

进阶问题
已知arr中存在 1 这个数,是否能够降低时间复杂度?

解决方案

原问题
方法一:暴力递归
1、首先确定递归每一层代表的意义:代表arr[i]是否需要相加,不加是一种命运,加是另一种命运,所以每一层递归再延伸出两个递归
2、其次确定每一次的递归需要哪些参数,[1]: arr, [2] : 当前位置 start ,[3]: 上层的和 [4]:sumSet用来存储所有和的可能性
3、最后遍历sumSet即可
方法二:动态规划
1、设计dp,dp[i]代表是否存在sum为i
2、循环遍历arr,其中再遍历dp的每一个角标j,用j-arr[i]在dp中标记true或者false代表是否已经出现
3、最后遍历dp求出未出现的数即可
进阶问题
详情看代码,解释看 思考和感悟

代码编写

java语言版本

原问题:
方法一:

    /**
     * 二轮测试:暴力递归算出所有可能性遍历即可
     * @param arr
     * @return
     */
    public static int unformedSumCp1(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        // 保存所有集合的和
        HashSet<Integer> sumSet = new HashSet<>();
        processCp1(arr, 0 , 0,sumSet);
        int min = Collections.min(sumSet);
        for (int i = min; ; i++) {
            if (!sumSet.contains(i)) {
                return i;
            }
        }
    }
    
    /**
     * 递归循环填充sumSet
     * @param arr
     * @param start 从 arr[start]开始计算
     * @param sum 前面的和
     * @param sumSet
     */
    private static void processCp1(int[] arr, int start, int sum, HashSet<Integer> sumSet) {
        if (start == arr.length) {
            return;
        }
        sumSet.add(sum + arr[start]);
        //当前位置有两种可能性,一种有start位置的和,一种无start位置的和
        processCp1(arr, start + 1, sum + arr[start], sumSet);
        processCp1(arr, start + 1, sum, sumSet);
    }

    public static void main(String[] args) {
        unformSumCp3(new int[]{1,2,4});
    }

方法二:

    /**
     * 方法二:通过动态规划的方式计算
     * @param arr
     */
    public static int unformeSumCp2(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        // 计算所有和
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        // dp[i]代表是否存在sum为i的集合
        boolean[] dp = new boolean[sum + 1];
        dp[0] = true;
        for (int i = 0; i < arr.length; i++) {
            for (int j = sum; j > 0; j--) {
                 if (j - arr[i] >= 0) {
                     dp[j] = dp[j - arr[i]] || dp[j];
                 }
            }
        }
        for (int i = 0; i < dp.length; i++) {
            if (!dp[i]) {
                return i;
            }
        }
        return sum+1;
    }

进阶问题


    /**
     * 进阶问题:如果数组中存在一个1,如何能够最快的求得答案
     * @param arr
     * @return
     */
    public static int unformSumCp3(int[] arr){
        if (arr == null || arr.length == 0) {
            return 0;
        }
        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;
    }

c语言版本

正在学习中

c++语言版本

正在学习中

思考感悟

1、首先说下递归和动态规划解法,这里动态规划我个人觉得没有从递归中总结出来的动态规划正常来说不容易学习到精髓,这里使用的类似于位示图的方法,如果arr中存在一个超级大的数和一个超级小的数,动态规划其实是存在空间浪费的缺陷的。
2、其次就是精简牛的进阶问题解法了,这个已知存在的一个1为什么能够简化过程了呢?
先给arr排个序
首先range这个变量是精髓,range代表什么呢?range代表遍历到arr[i]时[1…range]之间的每一个数都能够被arr[0…i-1]的某几个数相加得到,那么现在到arr[i]了,如果arr[i]在[1…range+1]之间,那么arr[i]加上[1…range+1]之间的任何一个数都能够保证从[1…2
range+1]之间没有断层,比如range+1可以由arr[i]和 range+1-arr[i]得到。
那么现在如果arr[i] > range+1会怎么样?
一定会出现一个断层数range+1,不管如何加都加不到,如果到这里理解了,那么也就理解了进阶问题的思路了。

所以为什么1能够简化过程?
1就是为了能够让range从0开始,让范围保持在了[1…range],如果最小值时2会如何?
如果最小值是2,那么后面的数都比2大或者等于2,则不可能存在和为3的数了,因为没有1与2相加,所以答案直接就是3了吧?

写在最后

方案和代码仅提供学习和思考使用,切勿随意滥用!如有错误和不合理的地方,务必批评指正~
如果需要git源码可邮件给2260755767@qq.com
再次感谢左大神对我算法的指点迷津!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

元空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值