☰
最大子数组和问题全解析:从暴力法到Kadane算法的优化之路
2026/10/13 8:59:24 网站建设 项目流程

这道题我见过太多次了,无论在笔试还是面试里,它出现的频率都高得吓人。不夸张地说,它几乎是"动态规划入门第一课"级别的存在——题目本身看起来很简单:一个整型数组,找出一段连续的子数组,让和最大。像题目里给的例子,{-2, 5, 3, -6, 4, -8, 6},一眼扫过去就知道答案是5 + 3 = 8,也就是下标 1 和下标 2 两个元素相加。

但真到了实现环节,很多人的第一反应还是三层循环暴力求解,然后被问到"能不能优化"的时候卡住。这篇东西我打算把从暴力到 Kadane 算法的完整推导过程写清楚,把每一步为什么这么做、边界在哪里、面试官会怎么追问都摊开来讲。不管你是刚学算法的萌新,还是准备跳槽要刷题的老手,看完应该都会有点收获。

1. 先搞清楚题目在问什么

1.1 子数组的定义和题目的隐含条件

这道题最容易被忽略的地方,是"子数组"这个词的准确定义。子数组指的是原数组中连续的一段,下标必须是紧挨着的。比如在{-2, 5, 3, -6, 4, -8, 6}里面,{5, 3}是一个子数组,{5, 3, -6, 4}也是一个子数组,但{5, -6, 6}就不是——因为这里的元素在原数组里并没有连续出现。

这个定义直接决定了后面所有解法的形态。很多人一做题就想着"从数组里挑几个数",那变成了另一个经典问题——最大子序列和(可以跳着选),两个问题解法完全不同,别搞混。

还有一个隐含条件:题目说的是整型数组,意味着元素可能是正数、负数、零,也可能全是负数。这个看似废话的细节,恰恰是后面很多坑的源头。初始化答案的时候如果拍脑袋设成 0,遇到全负数组就会直接翻车,这个我在第 5 部分会专门展开。

1.2 为什么暴力解法反而是一个好的起点

我见过不少学习者,上来就直奔最优解,看完 Kadane 算法的代码觉得"哇好简单",然后就过了。但真到了面试现场,面试官往往先问"你第一反应是怎么想的",而不是"请直接默写最优解"。

暴力的思路其实非常朴素:把所有可能的子数组都枚举一遍,每个子数组求个和,再找出最大值。这个思路天然正确,没有任何推导成本,而且代码写出来非常符合人类的直觉。哪怕是在真刀真枪的工程场景里,如果数组规模不大(比如长度在几十到几百),暴力的性能也完全可以接受。所以我一直建议:碰到这类问题,先写暴力,哪怕只是用来验证后续优化版本的正确性,也值得做。

暴力解的核心是枚举起点和终点。可以用两重循环枚举,i是起点,j是终点,然后从i到j累加求和。这么写是三层循环,时间复杂度 O(n³)。稍微优化一下,在枚举终点的过程中顺便累加,把第三层循环省掉,就变成了 O(n²)。这个优化过程本身就是一道很经典的"如何降低一个数量级复杂度"的练习题。

// 暴力解法,时间复杂度 O(n^2) int maxSubarraySum(int a[], int n) { int maxSum = a[0]; // 注意这里不能初始化为 0 for (int i = 0; i < n; i++) { int currentSum = 0; for (int j = i; j < n; j++) { currentSum += a[j]; if (currentSum > maxSum) { maxSum = currentSum; } } } return maxSum; }

这段代码的关键点在于maxSum的初始值。如果数组全为负数,初始化为 0 的话,最大值会被错误地算成 0,因为任何一个负数都比 0 小。所以这里初始化为a[0](数组第一个元素)是最稳妥的。当然,如果你确定数组里一定有正数,初始化为 0 也能过,但写代码的原则是处理所有边界情况,而不是赌输入数据。

2. 从暴力到一步到位的优化链条

2.1 暴力解法的瓶颈在哪里

O(n²) 的解法在数组长度为 10^3 时还能跑(大约十万次操作),到了 10^5 量级就是 10^10 次操作,现代 CPU 每秒大概能执行几十亿次简单运算,也就是说要跑好几秒甚至更久。而真实场景中,数组长度到达百万量级一点也不夸张,这时候就需要一个更聪明的办法。

暴力的本质是"枚举所有起点和终点"。但仔细想想,对于每一个位置,我们根本不关心"以它为终点的所有子数组",我们只关心"以它为终点的子数组中,和最大的那一个"。一旦有了这个视角,问题就从一个枚举问题变成了一个递推问题。

2.2 核心观察:以 i 结尾的最大子数组和

关键洞察只有一句话:以第 i 个元素结尾的所有子数组,要么只有第 i 个元素自己,要么是"以第 i-1 个元素结尾的最大子数组"再加上第 i 个元素。

为什么?因为一个以a[i]结尾的子数组,往前延伸时必然覆盖a[i-1]。如果我想让这个子数组的和最大,那前面那部分就必须是"以a[i-1]结尾的所有子数组中,和最大的那个"。这是典型的动态规划最优子结构。

用数学语言表达就是:

dp[i] = max(dp[i-1] + a[i], a[i])

其中dp[i]表示以a[i]结尾的子数组的最大和。

这个递推式就是 Kadane 算法(最大子数组和算法)的核心。它的直观含义是:如果前面那段子数组加上当前元素之后比当前元素单独还小,说明前面的子数组是"累赘",不如从当前元素重新开始。这就像爬山,如果前面积累的高度加上当前位置还不如原地起步高,那不如放弃前面的路,从这里重新出发。

2.3 用题目中的数据走一遍完整推导

纸上谈兵没有意义,我们真刀真枪地用手算一算题目里的数组{-2, 5, 3, -6, 4, -8, 6}。

下标 ia[i]dp[i-1] + a[i]a[i]dp[i]当前全局最大值
0-2不存在-2-2-2
15-2 + 5 = 3555
235 + 3 = 8388
3-68 + (-6) = 2-628
442 + 4 = 6468
5-86 + (-8) = -2-8-28
66-2 + 6 = 4668

看这张表,有几个点值得注意。下标 1 的时候,dp[0] + a[1] = -2 + 5 = 3,小于a[1] = 5,所以dp[1]选择从 5 重新开始。为什么?因为前面的-2对"以 5 结尾的子数组"来说是一个拖后腿的存在,抛弃它更划算。下标 2 的时候,dp[1] + a[2] = 5 + 3 = 8,大于3,所以延续前面的子数组{5, 3},得到最大值 8。

下标 3 加了一个-6,dp[3]降到了 2,但这不影响全局最大值 8,因为全局最大值记录的是历史上出现过的最大dp值,不是当前dp值。很多初学者在实现时犯的错误,就是只维护dp[i]而忘了维护"历史最大值",导致输出的是以最后一个元素结尾的子数组的最大和,而不是全数组的答案。

下标 6 是另一个容易困惑的地方:dp[5] + 6 = -2 + 6 = 4,小于6,所以重新开始。这里说明一个事实:dp[i]并不一定是单调递增或递减的,它只关心"以当前位置结尾的最大和",而这个最大和可以被一个很大的负数打断,然后重新起头。

2.4 空间优化的魔法:为什么只需要两个变量

看递推式dp[i] = max(dp[i-1] + a[i], a[i]),你会发现一个关键事实:计算dp[i]只需要dp[i-1],再往前的信息完全用不上。这意味着我们根本不需要一个数组来存所有dp值,只要两个变量滚动着用就行:

  • currentSum:当前这个位置的dp[i]
  • maxSum:到目前为止出现过的最大值

这就是 Kadane 算法的最终形态,时间复杂度 O(n),空间复杂度 O(1)。从 O(n²) 降到 O(n) 只靠一个递推思路的转变,这就是动态规划的魅力所在——不需要花哨的技巧,只需要抓住问题结构中的最优子结构。

3. 三种语言的 Kadane 算法落地与细节处理

3.1 C 语言实现与边界情况

用 C 语言实现 Kadane 算法,需要特别注意指针和数组长度的传参。这里给出一个能直接跑起来的完整函数:

int maxSubArray(int* nums, int numsSize) { if (numsSize <= 0) { // 根据题目约定处理空数组,这里抛个标志或者直接返回 0 return 0; } int currentSum = nums[0]; int maxSum = nums[0]; for (int i = 1; i < numsSize; i++) { // 核心递推:要么延续之前的子数组,要么从当前元素重新开始 if (currentSum + nums[i] > nums[i]) { currentSum = currentSum + nums[i]; } else { currentSum = nums[i]; } if (currentSum > maxSum) { maxSum = currentSum; } } return maxSum; }

这里的if-else其实就是max函数的展开写法。有些编译器对max支持不好,或者面试时不想引额外的库函数,手写if更稳妥。另外注意,循环从i = 1开始,因为i = 0的情况已经在初始化时处理掉了,否则会重复计算一次a[0]。

3.2 Python 实现与可读性优化

Python 的写法可以非常简洁,但简洁不代表难以理解。我通常先写一个"教学版",再写一个"精简版",面试时先讲教学版,再展示如何压缩。

def max_subarray_sum(nums): # 教学版:思路直白,适合面试讲解 if not nums: return 0 current_sum = nums[0] max_sum = nums[0] for num in nums[1:]: # 核心递推式:dp[i] = max(dp[i-1] + a[i], a[i]) current_sum = max(current_sum + num, num) max_sum = max(max_sum, current_sum) return max_sum

Python 的max函数内置,写起来非常顺手。但有个细节:nums[1:]会创建新的切片,如果数组很大,这个切片会额外占用内存。严格来说这不太符合 O(1) 空间的精神。用for i in range(1, len(nums))可以避免切片,但代码稍微啰嗦一点。在大数据量的场景下,我建议用range版本,虽然切片在现代 Python 中不一定真的复制(取决于具体实现),但养成不浪费内存的习惯总没错。

3.3 Java 实现与数组下标陷阱

Java 的写法介于 C 和 Python 之间,需要显式指定数组下标,正好可以展示一个容易踩坑的地方:

public int maxSubArray(int[] nums) { if (nums.length == 0) { return 0; } int currentSum = nums[0]; int maxSum = nums[0]; for (int i = 1; i < nums.length; i++) { currentSum = Math.max(nums[i], currentSum + nums[i]); maxSum = Math.max(maxSum, currentSum); } return maxSum; }

一个常见的错误是写for (int i = 0; i < nums.length; i++),然后currentSum = Math.max(nums[i], currentSum + nums[i])。这样写问题不大,因为i=0时算出来还是nums[0],但有些同学会把maxSum也放进循环里初始化,导致maxSum被覆盖两次。代码逻辑上没问题,但读起来绕。更关键的是,数组为空的判断必须放在最前面,否则nums[0]会抛数组越界异常。

3.4 四种典型测试用例与结果验证

写完代码不能只看题目给的例子就跑,要主动构造边界情况。我每次都会用下面这几组数据来验证:

测试用例数组期望输出原因
常规混合{-2, 5, 3, -6, 4, -8, 6}8题目原例
全负数{-3, -5, -2, -7}-2不能返回 0
全正数{1, 2, 3, 4}10所有元素加起来
单个元素{7}7最小规模
零元素{0, -1, 0, 3}3零不改变和,但也不能把 0 当最大值

特别是全负数这个用例,最能检验maxSum初始值是否写对了。如果初始化成 0,全负数组会错误地返回 0。这也是我在 1.2 节强调"初始化为a[0]"的原因。

4. 分治解法:另一种拆解问题的思路

4.1 分治的核心逻辑:答案藏在哪里

动态规划的思路是"顺着数组尾部延伸",而分治的思路是"把数组一刀切成两半"。对于一个数组a[l..r],它的最大子数组和只可能有三种情况:

  1. 完全在左半边a[l..mid]里
  2. 完全在右半边a[mid+1..r]里
  3. 跨越中点,左半边选一部分,右半边选一部分,拼成一个完整的子数组

前两种情况是递归的子问题,直接调用自身就能解决。第三种情况需要单独处理:从左半边最右边往左延伸,找到一个"以 mid 结尾的最大后缀和";从右半边最左边往右延伸,找到一个"以 mid+1 开头的最大前缀和",两段加起来就是跨中点的最大子数组和。

为什么第三种情况要这么算?因为只要一个子数组跨越了中点,它一定包含a[mid]和a[mid+1]。从这两个位置分别往两端延伸,延伸的原则是"只看这段的和够不够大,不用管整段是否最优"——跨越中点的子数组被拆成两半,每一半独立最优,合起来就是整体最优。这背后的思想和动态规划是一致的:全局最优来自局部最优的组合。

4.2 分治代码实现与复杂度分析

int maxCrossingSum(int a[], int left, int mid, int right) { // 左半边:从 mid 往左累加,找最大后缀和 int leftSum = a[mid]; int leftMax = a[mid]; for (int i = mid - 1; i >= left; i--) { leftSum += a[i]; if (leftSum > leftMax) { leftMax = leftSum; } } // 右半边:从 mid+1 往右累加,找最大前缀和 int rightSum = a[mid + 1]; int rightMax = a[mid + 1]; for (int i = mid + 2; i <= right; i++) { rightSum += a[i]; if (rightSum > rightMax) { rightMax = rightSum; } } return leftMax + rightMax; // 跨中点的最大子数组和 } int maxSubarraySumDivide(int a[], int left, int right) { if (left == right) { return a[left]; // 只有一个元素,它就是答案 } int mid = left + (right - left) / 2; int leftMax = maxSubarraySumDivide(a, left, mid); int rightMax = maxSubarraySumDivide(a, mid + 1, right); int crossMax = maxCrossingSum(a, left, mid, right); // 三者取最大 if (leftMax >= rightMax && leftMax >= crossMax) { return leftMax; } else if (rightMax >= leftMax && rightMax >= crossMax) { return rightMax; } else { return crossMax; } }

时间复杂度是 O(n log n),因为每层递归都要做一次 O(n) 的跨越计算,递归深度是 log n 层。空间复杂度是 O(log n),对应递归栈的深度。虽然整体不如 Kadane 算法的 O(n),但这个思路在很多扩展问题里很有用,比如无法用动态规划直接建模的场景,或者需要"最大子数组在哪个区间"这种额外信息时,分治的递归结构反而更方便。

分治代码里最容易写错的地方有两个。第一个是mid的计算,写成(left + right) / 2在left和right都很大时可能整数溢出,写成left + (right - left) / 2才是安全的。第二个是maxCrossingSum里左半边的初始值,有人习惯把leftMax初始化为一个极小数(比如INT_MIN),但如果数组只有一个元素,a[mid]本身就是那个值,直接初始化成a[mid]更不容易出错。

4.3 分治和动态规划的对比:什么时候用哪个

这两者不是竞争关系,而是互补关系。动态规划重在看"递推结构",适合能写出状态转移方程的题目;分治重在看"如何切分",适合能自然分成互不相交的几块、且合并逻辑清晰的题目。

对于最大子数组和这个问题,动态规划更快、代码更短,是面试中应该优先给出的解法。但如果你能同时讲出分治版本,并且说清楚两者的复杂度差异和各自的适用场景,面试官通常会眼前一亮——这说明你不只是背了一个算法模板,而是真的理解问题的结构。我见过不少候选人,Kadane 倒背如流,但一问到分治就卡壳,这种"只会一种解法"的情况在深度面试里是非常吃亏的。

5. 进阶变种与面试官最爱的追问方向

5.1 不只是求和:如何把最大子数组本身找出来

原始问题只要求和,但很多场景下我们需要的是"哪一段子数组"达到这个最大值。比如在做股票数据分析时,你不仅想知道最大收益,还想知道该买入和卖出的日期点。

实现方式很简单:在 Kadane 算法的基础上,每当currentSum重新开始(即currentSum = nums[i]),就把start临时标记为i;每当maxSum被更新,就同步记录end = i和bestStart = start。这样遍历结束后,bestStart到end就是最优子数组的下标区间。

int maxSubArrayWithRange(int a[], int n, int* bestStart, int* bestEnd) { int currentSum = a[0]; int maxSum = a[0]; int start = 0; *bestStart = 0; *bestEnd = 0; for (int i = 1; i < n; i++) { if (a[i] > currentSum + a[i]) { // 条件等价于 currentSum < 0,说明前面的子数组是累赘 currentSum = a[i]; start = i; } else { currentSum = currentSum + a[i]; } if (currentSum > maxSum) { maxSum = currentSum; *bestStart = start; *bestEnd = i; } } return maxSum; }

注意这里判断条件我换了个写法:a[i] > currentSum + a[i]等价于currentSum < 0。这意味着什么?只要前面累加的和是负数,它就是一个负资产,应该果断止损。这跟生活中的投资逻辑一模一样:一个持续亏损的仓位,该斩仓就斩仓,不要为了"已经投入的成本"继续往里搭钱。

5.2 环形数组变种:打破线性思维

面试官最喜欢的变种之一是:允许子数组跨越数组末尾和开头形成环形,求环形数组的最大子数组和。比如{5, -3, 5}这个数组,线性最大子数组和是 7(5 + (-3) + 5不对,线性最大是 5 或 5,但环形的答案是 10,取两个 5)。

这个变种的核心 trick 是:环形最大子数组和等于数组总和减去最小子数组和。为什么?因为在一个环上,你选了一段作为答案,那么剩下的那段一定是最小子数组。换个角度想:把环形问题转换成"要么直接用线性最大子数组和(跨过首尾的那段不如线性直接选的)、要么用总和减去线性最小子数组和(剔除掉一段负数)"。

实现上只需要把 Kadane 算法改成同时维护最大值和最小值:

int maxSubarraySumCircular(int a[], int n) { int totalSum = 0; int currentMax = 0; int maxSum = INT_MIN; int currentMin = 0; int minSum = INT_MAX; for (int i = 0; i < n; i++) { totalSum += a[i]; currentMax = (currentMax + a[i] > a[i]) ? (currentMax + a[i]) : a[i]; maxSum = (maxSum > currentMax) ? maxSum : currentMax; currentMin = (currentMin + a[i] < a[i]) ? (currentMin + a[i]) : a[i]; minSum = (minSum < currentMin) ? minSum : currentMin; } if (maxSum < 0) { return maxSum; // 全负数组,环形也不会更好,直接返回最大值 } return (totalSum - minSum > maxSum) ? (totalSum - minSum) : maxSum; }

这里有个隐蔽的边界:如果数组全部为负数,totalSum - minSum会等于 0,反而比正确的负数最大值还大。所以必须先判断maxSum是否小于 0,如果是就直接返回。这个细节我在真实面试里见过很多人写漏,属于典型的"看似简单实则暗藏杀机"的问题。

5.3 二维矩阵的最大子矩阵和

另一位高频变种是二维问题:给定一个m x n的矩阵,找一个子矩阵使元素之和最大。解决办法是把二维问题转成一维问题:枚举子矩阵的上边界top和下边界bottom,然后把每一列在这个区间内的元素之和算出来,得到一个一维数组,对这个一维数组跑 Kadane 算法。

int maxSubmatrixSum(int** matrix, int m, int n) { int maxSum = INT_MIN; for (int top = 0; top < m; top++) { int colSum[n]; memset(colSum, 0, sizeof(colSum)); for (int bottom = top; bottom < m; bottom++) { for (int col = 0; col < n; col++) { colSum[col] += matrix[bottom][col]; } // 对 colSum 跑 Kadane int current = colSum[0]; int best = colSum[0]; for (int col = 1; col < n; col++) { current = (current + colSum[col] > colSum[col]) ? (current + colSum[col]) : colSum[col]; best = (best > current) ? best : current; } if (best > maxSum) { maxSum = best; } } } return maxSum; }

复杂度是 O(m² · n),在一维 O(n) 的基础上增加了一个"枚举上下边界"的 O(m²) 循环。这个变种的价值在于,它展示了一个很重要的算法思维模式:把高维问题通过"压缩"降成一维问题。很多看起来复杂的题目,本质上都能拆解成若干轮已知问题。

5.4 面试现场:Kadane 之后的连环追问

如果你在面试中写出了 Kadane 算法,面试官通常不会直接让你走,而是会顺着往下追问:

  • "如果允许你重新排列数组中的元素,最大子数组和怎么求?"——这个问题的答案是把所有正数加起来就完了,因为你可以任意排列,正数就能连在一起。
  • "如果要求子数组长度至少为 k,怎么做?"——这就需要维护一个长度为 k 的滑动窗口,或者用前缀和加滑动窗口的最小值来求解。
  • "如果数组很长,但只允许遍历一次,而且不允许用额外空间,能做吗?"——这其实就是在引导你给出 Kadane 算法,因为它天然就是一遍扫描加 O(1) 空间。
  • "如果数组里有大量的值,比如上亿个元素,怎么在分布式环境里算?"——这会把问题导向分治思想,每个节点算局部最大前缀和、最大后缀和、最大子数组和,然后在合并节点组合这些信息。

这些追问看起来五花八门,背后其实都在考察你有没有真正理解"最大子数组和"这个问题的数据结构特征:前缀、后缀、连续段。与其背一堆答案,不如从原理上吃透递推式dp[i] = max(dp[i-1] + a[i], a[i])和分治的三种情况,追问再怎么变也离不开这些基础。

6. 常见问题与踩坑实录:那些年我犯过的错

6.1 maxSum 初始化成 0,全负数组直接翻车

这个坑我在初学阶段踩过,在面试别人的时候也见无数人踩过。很多人看题目里数组有正数,就理所当然地把答案初始化为 0,结果一到全负数组就输出 0,而正确答案应该是最大的那个负数(比如{-3, -5, -2}应该返回 -2)。

解决方式前面已经说过:初始化为a[0],或者用INT_MIN。但用INT_MIN有一个麻烦:如果数组长度是 0,你会返回一个莫名其妙的大负数。所以在处理空数组时,需要约定好返回什么(通常是 0 或者抛异常)。在刷题平台上,题目一般保证数组非空,但工程实践中必须考虑空数组的情况。

6.2 只维护 dp 数组、忘记全局最大值

我见过一种半吊子的实现:正确算出了dp[i],但最后直接返回dp[n-1],而不是max(dp[0..n-1])。这在数组单调递增时碰巧是对的,因为dp[i]一直在涨,最后一个就是最大的。但回到题目给的例子,最后一个dp值是 6,而全局最大值是 8,直接返回就错了。

这个错误的根源是:dp[i]的定义是"以 i 结尾"的最大和,而题目要的是"所有子数组"的最大和。两者不一定相等。所以maxSum这个变量是必须的,它不是优化,而是语义本身。

6.3 整数溢出:当 sum 超过 int 范围

题目里只说"整型数组",没说整数范围。在实际工程中,一个包含 10^6 个元素的 int 数组,如果每个元素都接近 2^31 - 1,子数组和会远远超过 int 的范围。在 C/C++/Java 中,这意味着有符号整型溢出,行为是未定义的(C/C++)或者回绕(Java)。

处理方式有几种:明确题目的约束范围;把累加变量声明为long或long long;或者在实际计算前预判是否会溢出。在面试中这个问题不常考,但在真实的数据处理场景里,我踩过一次"明明算法逻辑没问题,结果却诡异"的 bug,最后排查半天发现是 int 溢出,从那以后我在做累加题时都习惯性用更大的类型,这是很多老开发都有的肌肉记忆。

6.4 递归分治的栈溢出隐患

分治解法的递归深度是log n,理论上不会栈溢出,但如果递归函数写得有问题,比如mid计算不对导致无限递归,那就另当别论了。我在实现时习惯在递归函数开头加一个if (left > right)的防御性判断,虽然正常路径永远走不到,但真出了 bug 时能更快定位。

还有一个分治特有的坑:maxCrossingSum里如果数组只有两个元素,mid和mid+1都要存在,下标的边界要想清楚。我建议写完后无聊时拿{1, 2}、{1, 2, 3}、{-1, -2, -3}各跑一遍,确保没有越界。

6.5 动手总结一份自测清单

我每次写完一个算法题,都不会急着提交,而是会按下面的清单自查一遍:

  • 数组全为正数:答案应该是所有元素之和
  • 数组全为负数:答案应该是最大那个负数
  • 数组有正有负,但最大子数组恰好在开头或末尾
  • 单个元素的数组
  • 两个元素的数组
  • 最大子数组跨越数组中间位置
  • 随机数据,和暴力解法对拍验证(这个在本地写一小段脚本,对拍几百组随机数据,能发现大多数隐藏 bug)

对拍是性价比最高的测试方法:写一个简单但绝对正确的暴力版本,再写一个优化版本,然后用随机输入不断对比两者输出。只要有一次不一致,就说明优化版本有 bug。这个方法我已经用了很多年,几乎成了所有算法实现的标配步骤。第 1 节里我提到的"先写暴力",这时候就派上用场了——它不仅仅是思路的起点,更是验证最优解的标尺。

回到最初那个问题:{-2, 5, 3, -6, 4, -8, 6}的最大子数组和为什么是 8?因为5 + 3这一段和最大,后面-6 + 4把和拉低到 6,再后面的-8直接让累加归负,最后的 6 只能单独重新开始。Kadane 算法正是沿着这个"何时止损、何时重新开始"的思路,一遍扫描就能找到答案。

如果你刚开始学算法,我建议你亲手把这张 dp 表画几遍,再写三遍代码:暴力版、分治版、Kadane 版。每一版都会加深你对这个问题结构的理解。等你把这道题吃透了,再去看股票最大收益问题、最长连续递增序列、二维最大子矩阵和这些变种,会发现它们的底子其实是同一个东西。算法学习的乐趣就在于此:一道经典题,就是一把打开一类问题的钥匙。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询