You need to enable JavaScript to run this app. 递归实现固定长度组合求和加剪枝后耗时增加的原因与优化
优惠活动
大模型
产品
解决方案
定价
更多

递归实现固定长度组合求和加剪枝后耗时增加的原因与优化

为什么你的剪枝反而让代码变慢

你加的剪枝逻辑没有带来预期的提速,核心是逻辑写歪了,额外计算的成本超过了剪枝省下来的时间,具体拆成这几点:

  • 剪枝的循环边界完全写错了
    你本意是算「从当前候选位置i开始,取够还需要的元素个数,能拿到的最大和」,正常应该是取h[i], h[i+1], ..., h[i + (l-n_a) -1]这几个元素的和。但你写的循环是for(int x = i; x < l-n_a; x++),相当于不管i是多少,x的上界固定是l-n_a,和i根本没关系。举个实际例子:h=[30,20,19,10], l=2, k=38,当i=1(候选元素是20)时,你需要选2个元素,从i=1开始取的最大和应该是20+19=39,但你的循环x从1开始,x<2,只加了h[1]=20,算出来s=20,判断0+20<38直接return,反而把能凑出39的有效分支给乱剪了。这种错误的判断根本没精准砍掉计算成本高的长分支,反而经常在短分支上做无用判断。
  • 剪枝本身的计算成本太高
    原来的代码只在递归最底层(选够l个元素)的时候算一次数组总和。你现在每一层递归、每一次循环迭代,都要跑两层计算:一是临时循环算s值,二是调用sum函数遍历整个ans数组算当前已选的和。这些重复计算的累计开销,远超过错误剪枝省下来的那点时间——毕竟你本来要递归跑好几个分支才能完成的计算没被砍掉,反而在每个小循环里加了额外的求和循环,自然越跑越慢。
  • 剪枝的前置条件没做对
    你想的「后续最大和不够就剪」的逻辑,有个大前提:候选数组h必须是降序排列的,这样从某个位置往后取的前几个元素,才是当前能拿到的最大可能值。如果数组是乱序的,你算出来的s根本不是最大和,要么剪不掉,要么乱剪。哪怕你的测试用例刚好是降序的,你没做排序也没提前算好前缀/后缀和,每次都临时循环算s,也是纯纯的浪费。
  • return的位置用错了
    你在for循环里一旦判断不满足就直接return整个函数,本来如果数组是降序排好的,一旦当前i的位置凑不出最大和,后面所有i的候选元素更小,肯定也凑不出,这时候应该直接break循环,而不是乱return。不过在你这个逻辑里,因为循环边界错了,这个return反而没造成更大的混乱,只是又增加了无用的判断成本。
  • 原始代码本身的冗余成本很高
    你原来的代码每次循环都要手动开新数组、循环拷贝ans和候选h数组,这部分本来就是很大的固定开销,1.8s的耗时里很大一部分都花在这些数组拷贝上。你加剪枝根本没碰这部分的冗余,只是在上面叠了更多计算,自然提速不了。
正确的优化方案

顺着你的剪枝思路,把逻辑捋顺,光这一套操作就能把耗时压到毫秒级:

  1. 先给候选数组降序排序
    比如你的测试用例h[] = 20 19 10 8 8 7 7 7刚好是降序的,乱序的话必须先排,不然根本做不了上界判断。
  2. 提前算好后缀和,把临时求和的循环去掉
    预先生成一个数组suf[i],表示从位置i开始往后所有元素的和。这样你要算「从i开始取rem个元素的最大和」的时候,直接用suf[i] - suf[i+rem]就行,O(1)就能拿到结果,不用每次都循环加。
  3. 用回溯替代数组拷贝,维护全局当前和
    别每次递归都开新的newAns、ros数组了,就用一个固定长度的ans数组,选元素的时候把值放到ans[n_a]的位置,同时把元素值加到维护的current_sum变量里,然后递归下一层;递归回来的时候,把ans[n_a]清空、把current_sum减去这个值就行。这一步直接把原来所有的数组拷贝循环全省掉,至少能省70%的耗时。
  4. 剪枝逻辑改成O(1)判断+break
    对当前循环里的每个i,算一下从i开始取rem = l -n_a个元素的最大和,如果current_sum + 最大和 <k,直接break整个for循环就行——因为数组是降序的,后面i越大,候选元素越小,最大和只会更低,完全没必要再跑后面的循环了。这里注意是break,不是return。
  5. 把输出速度提上来
    你原来的代码用cout << endl每次都刷缓冲区,输出量稍大就会非常慢。可以在程序开头加两行把同步关掉:
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    同时把输出里的endl换成\n,别每次输出都手动刷缓冲区。

剪枝正确的效果示例

还是拿你的测试用例l=2,k=38,降序的h数组举例:

  • 第一层递归,n_a=0,current_sum=0,rem=2。i=0的时候,从0开始取2个元素的和是20+19=39,0+39>38,进入递归。
  • 下一层n_a=1,current_sum=20,rem=1,候选数组是从索引1开始的元素。i=0的时候,取19,current_sum+19=39>38,递归到底层输出39。
  • 回到上一层,i=1的时候,候选元素是10,rem=1,最大和是10,current_sum+10=30<38,直接break循环,不用再跑i=2、i=3...后面的候选了。
  • 再回到第一层,i=1的时候,候选元素是19,从1开始取2个元素的和是19+10=29,0+29<38,直接break循环,后面i=2、i=3...的循环全不用跑了。

这种情况下根本没跑几个无效分支,自然速度就上来了。


内容的提问来源于stack exchange,提问作者Akash Chattopadhyay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.09 11:36:00