You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归计算数组任意k个非连续元素最大和的最优算法及方案改进建议

求解数组中k个非连续元素的最大和

首先,咱们先明确问题核心:要从数组中选出k个互不相邻的元素,使得它们的和最大。你之前的递归代码没能得到正确结果,主要是因为没有把「选了多少个元素(k)」这个关键状态纳入递归逻辑里,而且终止条件和状态转移也没覆盖全场景。

问题分析与最优解法思路

最快的解法是动态规划(DP),时间复杂度为O(nk),空间可以优化到O(k),这是目前能达到的最优复杂度——因为你必须遍历每个元素,同时跟踪选了0到k个元素的状态,没办法再简化了。

动态规划状态定义

我们定义dp[i][j]表示:前i个元素中,选出j个非连续元素的最大和(这里数组索引从0开始,i对应数组的前i个元素,即a[0]到a[i-1])。

状态转移方程

对于第i个元素(也就是a[i-1]),有两种选择:

  1. 不选这个元素:那最大和就等于前i-1个元素选j个的最大和,即dp[i][j] = dp[i-1][j]
  2. 选这个元素:那前一个元素(i-1)就不能选,所以最大和等于前i-2个元素选j-1个的最大和加上当前元素的值,即dp[i][j] = dp[i-2][j-1] + a[i-1]

最终取这两种情况的最大值:
dp[i][j] = max(dp[i-1][j], dp[i-2][j-1] + a[i-1])

初始化条件

  • 当j=0时(选0个元素),不管多少个元素,和都是0:dp[i][0] = 0 对所有i成立
  • 当i<j时(元素数量少于要选的个数),不可能完成,所以设为极小值(比如-Infinity),表示无效状态
  • 当j=1且i>=1时,dp[i][1] = max(dp[i-1][1], a[i-1]),也就是前i个元素中最大的单个元素

代码实现(Java)

基础二维DP版本

public static long maxNonContiguousSum(int[] a, int k) {
    int n = a.length;
    if (k == 0) return 0;
    if (k > (n + 1) / 2) return -1; // 不可能选k个非连续元素的情况,比如n=5最多选3个
    
    // 初始化DP表
    long[][] dp = new long[n+1][k+1];
    
    // 初始化j>=1且i<j的情况为极小值
    for (int j = 1; j <= k; j++) {
        for (int i = 0; i < j; i++) {
            dp[i][j] = Long.MIN_VALUE;
        }
    }
    
    // 填充DP表
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= Math.min(k, i); j++) {
            // 不选当前元素的情况
            long notTake = dp[i-1][j];
            // 选当前元素的情况,需要保证i-2 >=0且j-1 >=0
            long take = (i >= 2 && dp[i-2][j-1] != Long.MIN_VALUE) ? (dp[i-2][j-1] + a[i-1]) : Long.MIN_VALUE;
            dp[i][j] = Math.max(notTake, take);
        }
    }
    
    return dp[n][k];
}

测试你给的例子:a={1,3,2,5,0,10},k=2,调用后返回15,和预期一致。

空间优化版本(O(k)空间)

因为dp[i][j]只依赖dp[i-1][j]和dp[i-2][j-1],我们可以用两个一维数组来替代二维数组:

public static long maxNonContiguousSumOptimized(int[] a, int k) {
    int n = a.length;
    if (k == 0) return 0;
    if (k > (n + 1) / 2) return -1;
    
    // prevPrev代表dp[i-2][...],prev代表dp[i-1][...]
    long[] prevPrev = new long[k+1];
    long[] prev = new long[k+1];
    
    // 初始化无效状态
    for (int j = 1; j <= k; j++) {
        prevPrev[j] = Long.MIN_VALUE;
        prev[j] = Long.MIN_VALUE;
    }
    prev[0] = 0;
    prevPrev[0] = 0;
    
    for (int i = 1; i <= n; i++) {
        long[] curr = new long[k+1];
        curr[0] = 0;
        for (int j = 1; j <= Math.min(k, i); j++) {
            long notTake = prev[j];
            long take = (i >=2 && prevPrev[j-1] != Long.MIN_VALUE) ? (prevPrev[j-1] + a[i-1]) : Long.MIN_VALUE;
            curr[j] = Math.max(notTake, take);
        }
        prevPrev = prev;
        prev = curr;
    }
    
    return prev[k];
}

记忆化递归版本

如果你更倾向于递归写法,需要加入备忘录来避免重复计算,递归函数的状态要包含「当前处理到的索引」和「已经选了多少个元素」:

import java.util.HashMap;
import java.util.Map;

public class Solution {
    private Map<String, Long> memo;
    private int[] a;
    
    public long maxNonContiguousSumRecursive(int[] a, int k) {
        this.a = a;
        this.memo = new HashMap<>();
        long result = rec(a.length - 1, k);
        return result == Long.MIN_VALUE ? -1 : result;
    }
    
    private long rec(int idx, int remaining) {
        // 终止条件
        if (remaining == 0) return 0; // 选够了k个,和为0(加上之前选的)
        if (idx < 0) return Long.MIN_VALUE; // 元素不够了,返回极小值
        
        String key = idx + "," + remaining;
        if (memo.containsKey(key)) return memo.get(key);
        
        // 选当前元素:剩下的要从idx-2开始选,remaining减1
        long take = rec(idx - 2, remaining - 1);
        if (take != Long.MIN_VALUE) {
            take += a[idx];
        }
        
        // 不选当前元素:从idx-1开始选,remaining不变
        long notTake = rec(idx - 1, remaining);
        
        long max = Math.max(take, notTake);
        memo.put(key, max);
        return max;
    }
}

为什么你的递归代码不对?

你之前的递归函数没有跟踪「已经选了多少个元素」,而且参数n的含义不明确,状态转移也只考虑了选当前元素加之前的和,没有处理「不选当前元素」的情况,也没有终止条件判断是否选够了k个,所以无法得到正确结果。

内容的提问来源于stack exchange,提问作者L m

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 06:22:50