LeetCode 热题 100-----13.最大子数组和

一、前置知识

在学这道题之前,我们先搞懂所有会用到的基础概念,不用怕,全是大白话,没有复杂术语,每一个点都讲透,确保你能跟上后续所有内容。

1. 核心概念:数组、整数数组、连续子数组

① 数组:简单说就是"把多个数据排成一排,按顺序存放",比如班级里的同学按座位排成一排,每个同学就是一个"元素",每个元素都有自己的"位置"(专业叫"索引")。

② 整数数组:数组里的每个元素都是整数(可以是正数,比如5、8;负数,比如-2、-5;0),这道题的输入nums就是"整数数组"。

③ 连续子数组(重点!这道题的核心要求):子数组就是"从数组里挑一段",但必须满足"连续"------不能跳着挑元素。比如数组[1,2,3,4],连续子数组可以是[1](只挑第一个)、[1,2](前两个)、[2,3,4](后三个),但不能是[1,3](跳了2)、[2,4](跳了3)。

补充:子数组最少包含1个元素(哪怕数组全是负数,也要挑最大的那个负数,比如数组[-5,-3,-2],最大子数组就是[-2],和为-2)。

2. 基础语法:变量、循环、条件判断

① 变量:用来"存数据"的容器,比如我们要存"最大和",就定义一个变量max_sum,把计算出来的和存进去,后续可以随时修改、调用。

② for循环:用来"重复做一件事",比如我们要遍历数组的每一个元素,就用for循环,从第一个元素(索引0)依次走到最后一个元素(索引n-1,n是数组长度)。

举个大白话例子:遍历数组[1,2,3],循环就是"先拿1,再拿2,最后拿3",重复3次(数组长度是3)。

③ 条件判断(if语句):用来"做选择",比如我们计算出一个子数组的和,要判断这个和是不是比当前的"最大和"大,如果是,就更新最大和。

3. 关键工具:常用常量、函数

① C++里的常用内容:

  • #include <vector>:引入"向量"(也就是C++里的数组,和我们说的数组完全一样,只是写法不同)的头文件,没有这句话,就不能用vector定义数组。

  • #include <climits>:引入"极限值"头文件,里面有INT_MIN(表示C++里能存的最小整数,大概是-2147483648),用来处理"数组全是负数"的情况(比如初始时把最大和设为INT_MIN,避免漏了负数)。

  • #include <algorithm>:引入"算法"头文件,里面有max()函数,用来比较两个数的大小,比如max(3,5),结果就是5。

  • using namespace std;:简化写法,比如原本要写std::vector,加了这句话后,直接写vector就行,不用多写std::。

  • vector<int>& nums:表示"传入一个整数数组nums",&符号表示"引用传递"(不用深懂,记住:这样写能节省内存,而且能直接操作原数组,不用复制一份)。

② Python里的常用内容:

  • from typing import List:用来指定函数参数的类型,List表示"列表"(也就是Python里的数组),nums: List[int]表示nums是一个整数列表。

  • float('-inf'):表示Python里的"负无穷大",和C++的INT_MIN作用一样,用来处理全负数数组(初始时最大和设为负无穷,不管遇到什么负数,都能更新成这个负数)。

  • len(nums):获取数组的长度,比如nums = [1,2,3],len(nums)就是3。

4. 算法基础:时间复杂度、空间复杂度

① 时间复杂度:表示"算法要做多少件事",值越小,算法越快。

  • O(n²):两层for循环,比如n=100,就要做100×100=10000件事,速度较慢(暴力法用这个)。

  • O(n):一层for循环,n=100,只做100件事,速度很快(动态规划、Kadane算法用这个)。

  • O(logn):递归或二分法用到,比如n=100,大概只做7件事(分治法用这个)。

② 空间复杂度:表示"算法需要占用多少内存",值越小,占用内存越少。

  • O(1):只用到几个变量(比如max_sum、current_sum),不占用额外内存(Kadane算法用这个)。

  • O(n):需要一个和原数组一样长的数组(比如动态规划的dp数组),占用内存中等。

  • O(logn):递归时用到"递归栈",占用内存较少(分治法用这个)。

5. 进阶基础:动态规划、递归

① 动态规划(DP):核心思想是"把大问题拆成小问题,用小问题的答案推大问题的答案"。

举个大白话例子:想求"以第5个元素结尾的最大子数组和",不用重新计算所有包含第5个元素的子数组,只需要看"以第4个元素结尾的最大子数组和"------如果第4个结尾的和是正的,就把第5个元素加上;如果是负的,就只取第5个元素(加了反而更小)。

② 递归:核心思想是"自己调用自己,把大区间拆成小区间,解决小区间后,合并结果"。

举个大白话例子:想求数组[1,2,3,4]的最大子数组和,先拆成左半[1,2]和右半[3,4],分别求左半、右半的最大和,再求"跨越中间元素(2和3)"的最大和,三者取最大就是整个数组的最大和。

二、题目解析

题目:给你一个整数数组nums,请你找出一个"连续子数组"(最少包含1个元素),这个子数组的和是所有连续子数组里最大的,最后返回这个最大和。

核心3个要求,再强调一遍:

  1. 连续:子数组不能跳元素,必须是数组中一段连续的片段(比如[4,-1,2,1]是连续的,[4,2,1]就不连续)。

  2. 最少1个元素:哪怕数组全是负数,也要选最大的那个负数(比如数组[-5,-3,-2],最大子数组是[-2],和为-2)。

  3. 返回值:不是返回最大子数组本身,而是返回这个子数组的"和"(比如示例1的输出是6,不是[4,-1,2,1])。

示例补充(逐句看):

示例1:输入nums = [-2,1,-3,4,-1,2,1,-5,4]

所有连续子数组的和里,[4,-1,2,1]的和是4+(-1)+2+1=6,是最大的,所以输出6。

示例2:输入nums = [1],只有1个元素,子数组只能是[1],和为1,输出1。

示例3:输入nums = [5,4,-1,7,8],最大子数组是整个数组,和为5+4+(-1)+7+8=23,输出23。

三、解法讲解(由易到难,暴力→动态规划→Kadane→分治,每步讲透)

所有解法都包含:思路(大白话)→ 代码(逐句注释)→ 调用流程→ 运行过程(结合示例一步步算)→ 主函数测试(含示例+自定义输入),确保能跟着走,能自己运行代码。

解法1:暴力枚举法(最直观,入门首选,能直接看懂)

1. 核心思路(大白话)

既然要找"最大和的连续子数组",那我们就把"所有可能的连续子数组"都找出来,计算每个子数组的和,然后记录下最大的那个和------这就是暴力法的核心,简单、直观,就是有点慢,但能帮我们理解题目本质。

具体步骤:

① 遍历所有"起始位置i":i表示子数组从哪个元素开始(比如i=0,就是从第一个元素开始;i=1,就是从第二个元素开始)。

② 对每个起始位置i,遍历所有"结束位置j":j必须≥i(因为子数组要连续,结束位置不能在起始位置前面),j表示子数组到哪个元素结束。

③ 累加i到j的所有元素,得到当前子数组的和,然后和"当前最大和"比较,如果更大,就更新最大和。

④ 遍历完所有子数组后,返回最大和。

2. C++代码(逐句注释,每句都讲含义)

cpp 复制代码
#include <vector>   // 引入向量(C++的数组)头文件,没有这句话就不能用vector
#include <climits>  // 引入极限值头文件,用来使用INT_MIN(最小整数)
#include <algorithm>// 引入算法头文件,用来使用max()函数(比较两个数的大小)
using namespace std;  // 简化写法,后续不用写std::vector,直接写vector

// 定义Solution类,LeetCode题目固定格式,里面放解题函数
class Solution {
public:
    // 定义解题函数maxSubArray,参数是整数数组nums(引用传递),返回值是int类型(最大和)
    int maxSubArray(vector<int>& nums) {
        // 1. 计算数组的长度n,nums.size()就是数组的元素个数
        int n = nums.size();
        // 2. 初始化最大和max_sum为INT_MIN(最小整数),防止数组全是负数时,漏了最小的负数
        int max_sum = INT_MIN;
        
        // 3. 第一层for循环:枚举所有起始位置i(从0到n-1,因为数组索引从0开始)
        for (int i = 0; i < n; i++) {
            // 4. 定义current_sum(当前子数组的和),初始化为0,每次换起始位置i,都要重置为0
            int current_sum = 0;
            // 5. 第二层for循环:枚举所有结束位置j(从i到n-1,j≥i,保证子数组连续)
            for (int j = i; j < n; j++) {
                // 6. 累加当前元素nums[j]到current_sum,得到i到j的子数组和
                current_sum += nums[j];
                // 7. 条件判断:如果当前子数组和current_sum比max_sum大,就更新max_sum
                if (current_sum > max_sum) {
                    max_sum = current_sum;
                }
            }
        }
        // 8. 遍历完所有子数组,返回最大和max_sum
        return max_sum;
    }
};

// 主函数(测试函数):用来运行代码,测试示例和自定义输入,可以直接复制运行
int main() {
    // 定义Solution对象sol,用来调用maxSubArray函数
    Solution sol;
    
    // 测试示例1:输入nums = [-2,1,-3,4,-1,2,1,-5,4],预期输出6
    vector<int> nums1 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int result1 = sol.maxSubArray(nums1);  // 调用解题函数,传入nums1,得到结果
    cout << "示例1输出:" << result1 << endl;  // 打印结果,应该输出6
    
    // 测试示例2:输入nums = [1],预期输出1
    vector<int> nums2 = {1};
    int result2 = sol.maxSubArray(nums2);
    cout << "示例2输出:" << result2 << endl;  // 打印结果,应该输出1
    
    // 测试示例3:输入nums = [5,4,-1,7,8],预期输出23
    vector<int> nums3 = {5, 4, -1, 7, 8};
    int result3 = sol.maxSubArray(nums3);
    cout << "示例3输出:" << result3 << endl;  // 打印结果,应该输出23
    
    // 自定义测试1:全负数数组,输入nums = [-5,-3,-2,-7],预期输出-2
    vector<int> nums4 = {-5, -3, -2, -7};
    int result4 = sol.maxSubArray(nums4);
    cout << "自定义测试1(全负数)输出:" << result4 << endl;  // 输出-2
    
    // 自定义测试2:全正数数组,输入nums = [1,2,3,4],预期输出10
    vector<int> nums5 = {1, 2, 3, 4};
    int result5 = sol.maxSubArray(nums5);
    cout << "自定义测试2(全正数)输出:" << result5 << endl;  // 输出10
    
    return 0;  // 主函数结束
}

3. Python代码(逐句注释,每句都讲含义)

python 复制代码
from typing import List  # 引入List类型,用来指定函数参数是整数列表(数组)

# 定义Solution类,LeetCode题目固定格式
class Solution:
    # 定义解题函数maxSubArray,参数nums是整数列表,返回值是int类型(最大和)
    def maxSubArray(self, nums: List[int]) -> int:
        # 1. 计算数组的长度n,len(nums)就是数组的元素个数
        n = len(nums)
        # 2. 初始化最大和max_sum为负无穷(float('-inf')),处理全负数数组
        max_sum = float('-inf')
        
        # 3. 第一层for循环:枚举所有起始位置i(range(n)表示0到n-1,数组索引从0开始)
        for i in range(n):
            # 4. 定义current_sum(当前子数组的和),初始化为0,换起始位置i就重置
            current_sum = 0
            # 5. 第二层for循环:枚举所有结束位置j(从i到n-1,j≥i,保证连续)
            for j in range(i, n):
                # 6. 累加当前元素nums[j]到current_sum,得到i到j的子数组和
                current_sum += nums[j]
                # 7. 条件判断:如果当前和比max_sum大,更新max_sum
                if current_sum > max_sum:
                    max_sum = current_sum
        # 8. 遍历完所有子数组,返回最大和
        return max_sum

# 主函数(测试函数):运行代码,测试示例和自定义输入,可直接复制运行
if __name__ == "__main__":
    # 实例化Solution对象sol,用来调用maxSubArray函数
    sol = Solution()
    
    # 测试示例1:输入nums = [-2,1,-3,4,-1,2,1,-5,4],预期输出6
    nums1 = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    result1 = sol.maxSubArray(nums1)  # 调用函数,传入nums1,获取结果
    print("示例1输出:", result1)  # 打印结果,输出6
    
    # 测试示例2:输入nums = [1],预期输出1
    nums2 = [1]
    result2 = sol.maxSubArray(nums2)
    print("示例2输出:", result2)  # 输出1
    
    # 测试示例3:输入nums = [5,4,-1,7,8],预期输出23
    nums3 = [5, 4, -1, 7, 8]
    result3 = sol.maxSubArray(nums3)
    print("示例3输出:", result3)  # 输出23
    
    # 自定义测试1:全负数数组,输入nums = [-5,-3,-2,-7],预期输出-2
    nums4 = [-5, -3, -2, -7]
    result4 = sol.maxSubArray(nums4)
    print("自定义测试1(全负数)输出:", result4)  # 输出-2
    
    # 自定义测试2:全正数数组,输入nums = [1,2,3,4],预期输出10
    nums5 = [1, 2, 3, 4]
    result5 = sol.maxSubArray(nums5)
    print("自定义测试2(全正数)输出:", result5)  # 输出10

4. 调用流程(能看懂的执行步骤)

以C++为例(Python流程完全一样,只是语法不同):

① 运行主函数main(),先定义Solution对象sol(用来调用解题函数)。

② 定义测试数组(比如nums1),然后调用sol.maxSubArray(nums1),进入解题函数。

③ 解题函数中,先计算数组长度n,初始化max_sum为INT_MIN,current_sum为0。

④ 执行第一层for循环(i从0到n-1),每次i变化,current_sum重置为0。

⑤ 执行第二层for循环(j从i到n-1),累加nums[j]到current_sum,每次累加后判断是否更新max_sum。

⑥ 所有循环结束,解题函数返回max_sum,主函数接收结果,打印输出。

5. 运行过程(结合示例1,一步步算,跟着算一遍就懂)

示例1输入:nums = [-2,1,-3,4,-1,2,1,-5,4],n=9。

① i=0(起始位置是第一个元素-2):

j=0:current_sum = -2 → max_sum从INT_MIN变成-2;

j=1:current_sum = -2+1 = -1 → max_sum变成-1;

j=2:current_sum = -1 + (-3) = -4 → 比-1小,不更新;

j=3:current_sum = -4 +4 =0 → 比-1大,max_sum变成0;

j=4:current_sum=0+(-1)=-1 → 不更新;

j=5:current_sum=-1+2=1 → max_sum变成1;

j=6:current_sum=1+1=2 → max_sum变成2;

j=7:current_sum=2+(-5)=-3 → 不更新;

j=8:current_sum=-3+4=1 → 不更新;

② i=1(起始位置是1):

j=1:current_sum=1 → max_sum变成1(和当前一样,不更新);

j=2:1+(-3)=-2 → 不更新;

j=3:-2+4=2 → max_sum变成2;

... 后续j继续遍历,current_sum最大到2,不超过之前的2;

③ i=3(起始位置是4,重点!):

j=3:current_sum=4 → max_sum变成4;

j=4:4+(-1)=3 → 不更新;

j=5:3+2=5 → max_sum变成5;

j=6:5+1=6 → max_sum变成6;

j=7:6+(-5)=1 → 不更新;

j=8:1+4=5 → 不更新;

④ 后续i=4、5、6、7、8,遍历后current_sum都不会超过6,所以最终max_sum=6,输出6。

6. 复杂度总结

时间复杂度:O(n²)(两层for循环,n是数组长度),比如n=1000,就要执行1000×1000=100万次,速度较慢;

空间复杂度:O(1)(只用到了max_sum、current_sum、n这3个变量,不占用额外内存)。

解法2:动态规划法(优化时间,入门DP,能懂的思路)

1. 核心思路(大白话,结合前置知识)

暴力法太慢,因为做了很多重复计算(比如计算i=0、j=3的和时,已经算了i=0、j=2的和,暴力法会重新算一遍)。动态规划就是"避免重复计算",把大问题拆成小问题。

具体思路(重点,逐句懂):

① 定义"状态":dp[i] 表示"以第i个元素结尾的最大子数组和"(i是数组索引,从0开始)。

解释:dp[i]只关注"结尾是第i个元素"的子数组,不管前面的元素,这样就能把"整个数组的最大子数组和"拆成"每个位置结尾的最大子数组和",最后取这些和的最大值即可。

② 状态转移方程(核心中的核心):

  • 如果 dp[i-1] > 0:说明"以第i-1个元素结尾的最大子数组和"是正的,把第i个元素加上,和会更大 → dp[i] = dp[i-1] + nums[i];

  • 如果 dp[i-1] ≤ 0:说明"以第i-1个元素结尾的最大子数组和"是负的(或0),加上第i个元素只会更小,不如直接取第i个元素本身 → dp[i] = nums[i];

③ 初始条件:dp[0] = nums[0](因为第0个元素结尾的子数组,只能是它自己,和就是它本身)。

④ 最终结果:dp数组中所有元素的最大值(因为最大子数组可能以任意一个元素结尾)。

2. C++代码(逐句注释,每句讲含义)

cpp 复制代码
#include <vector>
#include <algorithm>  // 用到max()函数,比较两个数的大小
using namespace std;

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        // 1. 计算数组长度n
        int n = nums.size();
        // 2. 定义dp数组,dp[i]表示以第i个元素结尾的最大子数组和,长度和nums一样
        vector<int> dp(n);
        // 3. 初始条件:第0个元素结尾的子数组,和就是它自己
        dp[0] = nums[0];
        // 4. 初始化最大和max_sum,初始值是dp[0](因为至少有一个元素)
        int max_sum = dp[0];
        
        // 5. 遍历数组,从第1个元素开始(i=1,因为i=0已经初始化)
        for (int i = 1; i < n; i++) {
            // 6. 核心:状态转移方程,判断dp[i-1]是否为正,决定dp[i]的值
            // max(nums[i], dp[i-1] + nums[i]):取"自己本身"和"自己+前面的最大和"中较大的那个
            dp[i] = max(nums[i], dp[i-1] + nums[i]);
            // 7. 更新全局最大和:如果当前dp[i]比max_sum大,就更新
            max_sum = max(max_sum, dp[i]);
        }
        // 8. 返回全局最大和
        return max_sum;
    }
};

// 主函数(测试函数,和暴力法一样,测试示例+自定义输入)
int main() {
    Solution sol;
    
    // 测试示例1:预期6
    vector<int> nums1 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    cout << "示例1输出:" << sol.maxSubArray(nums1) << endl;
    
    // 测试示例2:预期1
    vector<int> nums2 = {1};
    cout << "示例2输出:" << sol.maxSubArray(nums2) << endl;
    
    // 测试示例3:预期23
    vector<int> nums3 = {5, 4, -1, 7, 8};
    cout << "示例3输出:" << sol.maxSubArray(nums3) << endl;
    
    // 自定义测试1:全负数,预期-2
    vector<int> nums4 = {-5, -3, -2, -7};
    cout << "自定义测试1输出:" << sol.maxSubArray(nums4) << endl;
    
    // 自定义测试2:全正数,预期10
    vector<int> nums5 = {1, 2, 3, 4};
    cout << "自定义测试2输出:" << sol.maxSubArray(nums5) << endl;
    
    return 0;
}

3. Python代码(逐句注释,每句讲含义)

python 复制代码
from typing import List

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 1. 计算数组长度n
        n = len(nums)
        # 2. 定义dp数组,长度和nums一样,初始值全为0
        dp = [0] * n
        # 3. 初始条件:第0个元素结尾的子数组和,就是nums[0]
        dp[0] = nums[0]
        # 4. 初始化最大和max_sum,初始值为dp[0]
        max_sum = dp[0]
        
        # 5. 遍历数组,从第1个元素开始(i=1)
        for i in range(1, n):
            # 6. 状态转移方程:取"自己本身"和"自己+前面的最大和"中较大的
            dp[i] = max(nums[i], dp[i-1] + nums[i])
            # 7. 更新全局最大和
            max_sum = max(max_sum, dp[i])
        # 8. 返回最大和
        return max_sum

# 主函数(测试函数)
if __name__ == "__main__":
    sol = Solution()
    
    # 测试示例1:预期6
    nums1 = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    print("示例1输出:", sol.maxSubArray(nums1))
    
    # 测试示例2:预期1
    nums2 = [1]
    print("示例2输出:", sol.maxSubArray(nums2))
    
    # 测试示例3:预期23
    nums3 = [5, 4, -1, 7, 8]
    print("示例3输出:", sol.maxSubArray(nums3))
    
    # 自定义测试1:全负数,预期-2
    nums4 = [-5, -3, -2, -7]
    print("自定义测试1输出:", sol.maxSubArray(nums4))
    
    # 自定义测试2:全正数,预期10
    nums5 = [1, 2, 3, 4]
    print("自定义测试2输出:", sol.maxSubArray(nums5))

4. 调用流程(和暴力法类似,简化了循环)

① 主函数定义测试数组,调用sol.maxSubArray(nums),进入解题函数。

② 解题函数中,计算n,定义dp数组,初始化dp[0]和max_sum。

③ 一层for循环(i从1到n-1),每次计算dp[i](用状态转移方程),然后更新max_sum。

④ 循环结束,返回max_sum,主函数打印结果。

5. 运行过程(结合示例1,一步步算dp数组,跟着算)

示例1输入:nums = [-2,1,-3,4,-1,2,1,-5,4],n=9。

① 初始化:dp[0] = -2,max_sum = -2。

② i=1(nums[1]=1):

dp[0] = -2 ≤ 0 → dp[1] = nums[1] = 1;max_sum = max(-2, 1) = 1。

③ i=2(nums[2]=-3):

dp[1] = 1 > 0 → dp[2] = 1 + (-3) = -2;max_sum = max(1, -2) = 1。

④ i=3(nums[3]=4):

dp[2] = -2 ≤ 0 → dp[3] = 4;max_sum = max(1, 4) = 4。

⑤ i=4(nums[4]=-1):

dp[3] = 4 > 0 → dp[4] = 4 + (-1) = 3;max_sum = max(4, 3) = 4。

⑥ i=5(nums[5]=2):

dp[4] = 3 > 0 → dp[5] = 3 + 2 = 5;max_sum = max(4, 5) = 5。

⑦ i=6(nums[6]=1):

dp[5] = 5 > 0 → dp[6] = 5 + 1 = 6;max_sum = max(5, 6) = 6。

⑧ i=7(nums[7]=-5):

dp[6] = 6 > 0 → dp[7] = 6 + (-5) = 1;max_sum = max(6, 1) = 6。

⑨ i=8(nums[8]=4):

dp[7] = 1 > 0 → dp[8] = 1 + 4 = 5;max_sum = max(6, 5) = 6。

最终dp数组:[-2,1,-2,4,3,5,6,1,5],最大值是6,输出6。

6. 复杂度总结

时间复杂度:O(n)(只有一层for循环,n是数组长度),比暴力法快很多;

空间复杂度:O(n)(需要一个dp数组,长度和原数组一样)。

解法3:Kadane算法(最优解,面试首选,空间优化版动态规划)

1. 核心思路(大白话,优化动态规划的空间)

观察动态规划法,我们发现:dp[i] 只依赖于 dp[i-1](计算dp[i]时,只需要知道dp[i-1]的值,不需要整个dp数组)。

所以,我们可以用"一个变量"代替"整个dp数组",把空间复杂度从O(n)优化到O(1),这就是Kadane算法------本质是"空间优化后的动态规划",思路和动态规划完全一样,只是更省内存。

具体思路:

① 用 current_max 代替 dp[i]:current_max 表示"以当前元素结尾的最大子数组和"(和dp[i]含义一样)。

② 用 max_sum 记录全局最大和(和动态规划一样)。

③ 初始化:current_max 和 max_sum 都等于 nums[0](因为第一个元素结尾的子数组和就是它自己)。

④ 遍历数组(从第二个元素开始):

  • current_max = max(nums[i], current_max + nums[i])(和动态规划的状态转移方程一样);

  • max_sum = max(max_sum, current_max)(更新全局最大和)。

⑤ 最终返回 max_sum。

2. C++代码(逐句注释,每句讲含义)

cpp 复制代码
#include <vector>
#include <algorithm>  // 用到max()函数
using namespace std;

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        // 1. 初始化current_max(当前结尾的最大和)和max_sum(全局最大和)
        // 都等于nums[0],因为第一个元素结尾的子数组和就是它自己
        int current_max = nums[0];
        int max_sum = nums[0];
        // 2. 计算数组长度n
        int n = nums.size();
        
        // 3. 遍历数组,从第二个元素开始(i=1)
        for (int i = 1; i < n; i++) {
            // 4. 核心:和动态规划的状态转移方程一样,判断current_max是否为正
            current_max = max(nums[i], current_max + nums[i]);
            // 5. 更新全局最大和
            max_sum = max(max_sum, current_max);
        }
        // 6. 返回全局最大和
        return max_sum;
    }
};

// 主函数(测试函数,和前面一致)
int main() {
    Solution sol;
    
    vector<int> nums1 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    cout << "示例1输出:" << sol.maxSubArray(nums1) << endl;  // 6
    
    vector<int> nums2 = {1};
    cout << "示例2输出:" << sol.maxSubArray(nums2) << endl;  // 1
    
    vector<int> nums3 = {5, 4, -1, 7, 8};
    cout << "示例3输出:" << sol.maxSubArray(nums3) << endl;  // 23
    
    vector<int> nums4 = {-5, -3, -2, -7};
    cout << "自定义测试1输出:" << sol.maxSubArray(nums4) << endl;  // -2
    
    vector<int> nums5 = {1, 2, 3, 4};
    cout << "自定义测试2输出:" << sol.maxSubArray(nums5) << endl;  // 10
    
    return 0;
}

3. Python代码(逐句注释,每句讲含义)

python 复制代码
from typing import List

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 1. 初始化current_max和max_sum,都等于nums[0]
        current_max = max_sum = nums[0]
        
        # 2. 遍历数组,从第二个元素开始(nums[1:]表示从索引1到最后)
        for num in nums[1:]:
            # 3. 核心:状态转移,判断current_max是否为正
            current_max = max(num, current_max + num)
            # 4. 更新全局最大和
            max_sum = max(max_sum, current_max)
        # 5. 返回全局最大和
        return max_sum

# 主函数(测试函数)
if __name__ == "__main__":
    sol = Solution()
    
    nums1 = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    print("示例1输出:", sol.maxSubArray(nums1))  # 6
    
    nums2 = [1]
    print("示例2输出:", sol.maxSubArray(nums2))  # 1
    
    nums3 = [5, 4, -1, 7, 8]
    print("示例3输出:", sol.maxSubArray(nums3))  # 23
    
    nums4 = [-5, -3, -2, -7]
    print("自定义测试1输出:", sol.maxSubArray(nums4))  # -2
    
    nums5 = [1, 2, 3, 4]
    print("自定义测试2输出:", sol.maxSubArray(nums5))  # 10

4. 调用流程(和动态规划几乎一样,更简洁)

① 主函数调用sol.maxSubArray(nums),进入解题函数。

② 初始化current_max和max_sum为nums[0]。

③ 一层for循环,遍历从第二个元素开始的所有元素,每次更新current_max和max_sum。

④ 循环结束,返回max_sum,主函数打印结果。

5. 运行过程(结合示例1,和动态规划一致,只是用变量代替数组)

示例1输入:nums = [-2,1,-3,4,-1,2,1,-5,4]。

① 初始化:current_max = -2,max_sum = -2。

② 遍历num=1(第二个元素):

current_max = max(1, -2+1)=1;max_sum = max(-2,1)=1。

③ 遍历num=-3(第三个元素):

current_max = max(-3, 1+(-3))=-2;max_sum = max(1,-2)=1。

④ 遍历num=4(第四个元素):

current_max = max(4, -2+4)=4;max_sum = max(1,4)=4。

⑤ 遍历num=-1(第五个元素):

current_max = max(-1,4+(-1))=3;max_sum = max(4,3)=4。

⑥ 遍历num=2(第六个元素):

current_max = max(2,3+2)=5;max_sum = max(4,5)=5。

⑦ 遍历num=1(第七个元素):

current_max = max(1,5+1)=6;max_sum = max(5,6)=6。

⑧ 遍历num=-5(第八个元素):

current_max = max(-5,6+(-5))=1;max_sum = max(6,1)=6。

⑨ 遍历num=4(第九个元素):

current_max = max(4, 1+4)=5;max_sum = max(6,5)=6。

最终current_max循环更新结束,max_sum始终为6,所以输出6,和动态规划、暴力法结果一致。

6. 复杂度总结

时间复杂度:O(n)(仅一层for循环,遍历数组一次,n是数组长度),是所有解法中最快的;

空间复杂度:O(1)(只用到current_max和max_sum两个变量,不占用任何额外内存),是最优的空间效率。

补充:Kadane算法是面试中最常考的解法,必须掌握,代码简洁、效率高,记住核心的状态转移逻辑即可。

解法4:分治法(进阶拓展,理解递归思想,可逐步吃透)

1. 核心思路(大白话,结合前置递归知识)

分治法的核心是"分而治之"------把一个大问题拆成两个小问题,分别解决小问题,再把小问题的结果合并,得到大问题的答案。对应这道题,具体思路如下:

① 拆分:把整个数组分成"左半部分"和"右半部分"(找数组的中间位置mid,左半是[left, mid],右半是[mid+1, right])。

② 求解子问题:递归求解"左半部分的最大子数组和"(left_max)和"右半部分的最大子数组和"(right_max)。

③ 合并:最大子数组有3种可能性,取这3种情况的最大值,就是整个数组的最大子数组和:

  1. 最大子数组完全在左半部分(和为left_max);

  2. 最大子数组完全在右半部分(和为right_max);

  3. 最大子数组跨越中间元素mid(和为cross_max,即左半部分"以mid结尾的最大和" + 右半部分"以mid+1开头的最大和")。

④ 终止条件:当left == right时(数组只有一个元素),最大子数组和就是这个元素本身(因为子数组最少包含1个元素)。

补充:分治法虽然不是最优解(时间复杂度比Kadane算法高),但能帮我们理解递归思想,适合进阶学习,面试中偶尔会问到。

2. C++代码(逐句注释,每句讲含义,能看懂)

cpp 复制代码
#include <vector>
#include <climits>  // 用到INT_MIN(最小整数),处理全负数情况
#include <algorithm> // 用到max()函数,比较多个数的大小
using namespace std;

class Solution {
private:
    // 辅助函数1:计算"跨越中间元素mid"的最大子数组和(核心辅助函数)
    // 参数:nums(原数组)、left(左半部分起始索引)、mid(中间索引)、right(右半部分结束索引)
    int crossSum(vector<int>& nums, int left, int mid, int right) {
        // 第一步:计算左半部分"以mid结尾"的最大和(从mid往左遍历,累加找最大)
        int left_sum = INT_MIN;  // 左半部分最大和,初始化为最小整数
        int current = 0;         // 临时累加和,初始化为0
        // 从mid往左遍历(i从mid到left,每次减1),因为要找"以mid结尾"的最大和
        for (int i = mid; i >= left; i--) {
            current += nums[i];  // 累加当前元素
            left_sum = max(left_sum, current);  // 更新左半部分最大和
        }
        
        // 第二步:计算右半部分"以mid+1开头"的最大和(从mid+1往右遍历,累加找最大)
        int right_sum = INT_MIN; // 右半部分最大和,初始化为最小整数
        current = 0;             // 临时累加和重置为0
        // 从mid+1往右遍历(i从mid+1到right),因为要找"以mid+1开头"的最大和
        for (int i = mid+1; i <= right; i++) {
            current += nums[i];  // 累加当前元素
            right_sum = max(right_sum, current);  // 更新右半部分最大和
        }
        
        // 跨越中间的最大和 = 左半结尾最大和 + 右半开头最大和
        return left_sum + right_sum;
    }

    // 辅助函数2:递归函数,求解区间[left, right]内的最大子数组和
    int helper(vector<int>& nums, int left, int right) {
        // 终止条件:当区间只有一个元素(left == right),最大和就是这个元素本身
        if (left == right) return nums[left];
        
        // 1. 找中间索引mid,拆分左右区间(整数除法,避免溢出)
        int mid = (left + right) / 2;
        
        // 2. 递归求解左半区间[left, mid]的最大和
        int left_max = helper(nums, left, mid);
        
        // 3. 递归求解右半区间[mid+1, right]的最大和
        int right_max = helper(nums, mid+1, right);
        
        // 4. 计算跨越中间元素的最大和
        int cross_max = crossSum(nums, left, mid, right);
        
        // 5. 返回左、右、跨越中间三者中的最大值,就是当前区间的最大和
        return max(max(left_max, right_max), cross_max);
    }

public:
    // 主解题函数:调用递归辅助函数,初始区间是[0, nums.size()-1](整个数组)
    int maxSubArray(vector<int>& nums) {
        // 调用helper函数,传入整个数组的区间(从0到最后一个元素)
        return helper(nums, 0, nums.size()-1);
    }
};

// 主函数(测试函数,和前面解法一致,测试示例+自定义输入)
int main() {
    Solution sol;
    
    // 测试示例1:预期6
    vector<int> nums1 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    cout << "示例1输出:" << sol.maxSubArray(nums1) << endl;
    
    // 测试示例2:预期1
    vector<int> nums2 = {1};
    cout << "示例2输出:" << sol.maxSubArray(nums2) << endl;
    
    // 测试示例3:预期23
    vector<int> nums3 = {5, 4, -1, 7, 8};
    cout << "示例3输出:" << sol.maxSubArray(nums3) << endl;
    
    // 自定义测试1:全负数,预期-2
    vector<int> nums4 = {-5, -3, -2, -7};
    cout << "自定义测试1输出:" << sol.maxSubArray(nums4) << endl;
    
    // 自定义测试2:全正数,预期10
    vector<int> nums5 = {1, 2, 3, 4};
    cout << "自定义测试2输出:" << sol.maxSubArray(nums5) << endl;
    
    return 0;
}

3. Python代码(逐句注释,每句讲含义,能看懂)

php 复制代码
from typing import List  # 引入List类型,指定参数类型

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 辅助函数1:计算跨越中间元素mid的最大子数组和
        def cross_sum(left, mid, right):
            # 计算左半部分"以mid结尾"的最大和(从mid往左遍历)
            left_sum = float('-inf')  # 左半最大和,初始化为负无穷(处理全负数)
            current = 0               # 临时累加和,初始化为0
            # 从mid往左遍历,range(mid, left-1, -1)表示从mid到left,步长为-1(往左走)
            for i in range(mid, left-1, -1):
                current += nums[i]  # 累加当前元素
                left_sum = max(left_sum, current)  # 更新左半最大和
            
            # 计算右半部分"以mid+1开头"的最大和(从mid+1往右遍历)
            right_sum = float('-inf')  # 右半最大和,初始化为负无穷
            current = 0                # 临时累加和重置为0
            # 从mid+1往右遍历,到right结束
            for i in range(mid+1, right+1):
                current += nums[i]  # 累加当前元素
                right_sum = max(right_sum, current)  # 更新右半最大和
            
            # 跨越中间的最大和 = 左半结尾最大和 + 右半开头最大和
            return left_sum + right_sum
        
        # 辅助函数2:递归函数,求解区间[left, right]的最大子数组和
        def helper(left, right):
            # 终止条件:区间只有一个元素,最大和就是这个元素
            if left == right:
                return nums[left]
            
            # 1. 找中间索引mid,拆分左右区间(整数除法)
            mid = (left + right) // 2
            
            # 2. 递归求解左半区间[left, mid]的最大和
            left_max = helper(left, mid)
            
            # 3. 递归求解右半区间[mid+1, right]的最大和
            right_max = helper(mid+1, right)
            
            # 4. 计算跨越中间元素的最大和
            cross_max = cross_sum(left, mid, right)
            
            # 5. 返回三者中的最大值,作为当前区间的最大和
            return max(max(left_max, right_max), cross_max)
        
        # 主调用:求解整个数组的最大和,初始区间是[0, len(nums)-1]
        return helper(0, len(nums)-1)

# 主函数(测试函数,和前面一致)
if __name__ == "__main__":
    sol = Solution()
    
    # 测试示例1:预期6
    nums1 = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
    print("示例1输出:", sol.maxSubArray(nums1))
    
    # 测试示例2:预期1
    nums2 = [1]
    print("示例2输出:", sol.maxSubArray(nums2))
    
    # 测试示例3:预期23
    nums3 = [5, 4, -1, 7, 8]
    print("示例3输出:", sol.maxSubArray(nums3))
    
    # 自定义测试1:全负数,预期-2
    nums4 = [-5, -3, -2, -7]
    print("自定义测试1输出:", sol.maxSubArray(nums4))
    
    # 自定义测试2:全正数,预期10
    nums5 = [1, 2, 3, 4]
    print("自定义测试2输出:", sol.maxSubArray(nums5))

4. 调用流程(能看懂的递归执行步骤)

以C++为例(Python递归流程完全一致,只是语法不同):

① 主函数调用sol.maxSubArray(nums),进入主解题函数,主函数调用helper(nums, 0, n-1)(n是数组长度),开始递归。

② helper函数中,先判断left是否等于right:如果是,返回当前元素;如果不是,找mid,拆分左右区间。

③ 递归调用helper(nums, left, mid),求解左半区间的最大和(left_max);再递归调用helper(nums, mid+1, right),求解右半区间的最大和(right_max)。

④ 调用crossSum函数,计算跨越mid的最大和(cross_max)。

⑤ 比较left_max、right_max、cross_max,返回三者中的最大值,作为当前区间的最大和,回溯到上一层递归。

⑥ 所有递归结束后,最终返回的就是整个数组的最大子数组和,主函数接收结果并打印。

补充:递归的核心是"先拆后合",把大数组拆成一个个小数组,直到只剩一个元素,再逐步合并结果,可以想象成"拆分积木,再拼起来"。

5. 运行过程(结合示例1,一步步拆解递归,跟着走)

示例1输入:nums = [-2,1,-3,4,-1,2,1,-5,4],n=9,初始调用helper(0,8)(left=0,right=8)。

第一步:拆分整个数组(left=0,right=8)

mid = (0+8)/2 = 4,拆分左半区间[0,4]、右半区间[5,8]。

第二步:递归求解左半区间[0,4](nums[0-4] = [-2,1,-3,4,-1])

mid = (0+4)/2 = 2,拆分左半[0,2]、右半[3,4]。

  1. 递归求解[0,2](nums[0-2] = [-2,1,-3]):

mid = (0+2)/2 = 1,拆分左半[0,1]、右半[2,2]。

a. 求解[2,2]:left==right,返回nums[2] = -3(left_max1 = -3);

b. 求解[0,1](nums[0-1] = [-2,1]):

mid = 0,拆分左半[0,0]、右半[1,1];

  • 求解[0,0]:返回-2(left_max2 = -2);

  • 求解[1,1]:返回1(right_max2 = 1);

  • 计算cross_sum(0,0,1):左半结尾最大和=-2,右半开头最大和=1,和为-1;

  • 0,1\]的最大和 = max(-2,1,-1) = 1(left_max3 = 1);

d. [0,2]的最大和 = max(1, -3, -2) = 1(left_max4 = 1);

  1. 递归求解[3,4](nums[3-4] = [4,-1]):

mid = 3,拆分左半[3,3]、右半[4,4];

a. 求解[3,3]:返回4(left_max5 = 4);

b. 求解[4,4]:返回-1(right_max5 = -1);

c. 计算cross_sum(3,3,4):左半结尾最大和=4,右半开头最大和=-1,和为3;

d. [3,4]的最大和 = max(4, -1, 3) = 4(right_max4 = 4);

  1. 计算cross_sum(0,2,4):左半结尾最大和([0-2]结尾最大和1)=1,右半开头最大和([3-4]开头最大和4 + (-1)=3)=3,和为4;

  2. 0,4\]的最大和 = max(1,4,4) = 4(left_max = 4);

mid = (5+8)/2 = 6,拆分左半[5,6]、右半[7,8]。

  1. 递归求解[5,6](nums[5-6] = [2,1]):

mid = 5,拆分左半[5,5]、右半[6,6];

a. 求解[5,5]:返回2(left_max6 = 2);

b. 求解[6,6]:返回1(right_max6 = 1);

c. 计算cross_sum(5,5,6):左半结尾最大和=2,右半开头最大和=1,和为3;

d. [5,6]的最大和 = max(2,1,3) = 3(left_max7 = 3);

  1. 递归求解[7,8](nums[7-8] = [-5,4]):

mid = 7,拆分左半[7,7]、右半[8,8];

a. 求解[7,7]:返回-5(left_max8 = -5);

b. 求解[8,8]:返回4(right_max8 = 4);

c. 计算cross_sum(7,7,8):左半结尾最大和=-5,右半开头最大和=4,和为-1;

d. [7,8]的最大和 = max(-5,4,-1) = 4(right_max7 = 4);

  1. 计算cross_sum(5,6,8):左半结尾最大和([5-6]结尾最大和3)=3,右半开头最大和([7-8]开头最大和-5+4=-1)=-1,和为2;

  2. 5,8\]的最大和 = max(3,4,2) = 4(right_max = 4);

cross_sum(0,4,8):

① 左半[0,4]结尾最大和:从mid=4往左遍历,累加结果为-1(4-1)、3(4-1+4)、0(4-1+4-3)、1(4-1+4-3+1)、-1(4-1+4-3+1-2),最大和为3;

② 右半[5,8]开头最大和:从mid+1=5往右遍历,累加结果为2、3、-2、2,最大和为3;

③ cross_max = 3 + 3 = 6;

第五步:合并结果

整个数组的最大和 = max(left_max=4, right_max=4, cross_max=6) = 6,输出6。

补充:递归过程看似复杂,但核心是"拆分到最小,再逐步合并",不用死记每一步,重点理解"左、右、跨越中间"三个情况的合并逻辑即可。

6. 复杂度总结

时间复杂度:O(nlogn)(每次拆分数组为2份,共拆分logn层,每层遍历数组一次,总操作次数为n×logn);

空间复杂度:O(logn)(递归时用到"递归栈",栈的深度就是拆分的层数,即logn)。

补充:分治法的时间复杂度比Kadane算法高,空间复杂度比Kadane算法高,所以实际面试中,优先用Kadane算法,分治法主要用来理解递归思想。

四、整体总结(必看,梳理重点,快速掌握)

这道题(最大子数组和)是算法入门的经典题,四种解法从易到难,覆盖了暴力枚举、动态规划、空间优化、递归分治,可以按以下顺序学习,逐步提升:

  1. 入门首选:暴力枚举法
  • 优点:思路最简单、最直观,能快速理解"连续子数组"和"最大和"的核心要求;

  • 缺点:时间复杂度O(n²),速度慢,不适合大数据量;

  • 用途:帮建立对题目的基本认知,打好基础。

  1. 过渡学习:动态规划法
  • 优点:时间复杂度优化到O(n),避免了暴力法的重复计算;

  • 缺点:空间复杂度O(n),需要额外的dp数组;

  • 用途:理解"动态规划"的核心思想(拆分子问题、状态转移),为学习Kadane算法铺垫。

  1. 面试必备:Kadane算法(最优解)
  • 优点:时间复杂度O(n)、空间复杂度O(1),效率最高,代码简洁;

  • 核心:用一个变量代替dp数组,本质是空间优化后的动态规划;

  • 要求:必须熟练掌握,面试中遇到这道题,直接写Kadane算法即可。

  1. 进阶拓展:分治法
  • 优点:能锻炼递归思维,适合理解"分而治之"的算法思想;

  • 缺点:效率不如Kadane算法,实际应用较少;

  • 用途:进阶学习,拓宽算法思路,应对面试中对递归的考察。

相关推荐
0xR3lativ1ty1 小时前
大模型算法原理高频题解析
算法
故事还在继续吗1 小时前
STL 容器算法手册
开发语言·c++·算法
田梓燊1 小时前
力扣:94.二叉树的中序遍历
数据结构·算法·leetcode
啊我不会诶1 小时前
2023西安邀请赛vp补题
c++·算法
khalil10201 小时前
代码随想录算法训练营Day-38动态规划06 | 322. 零钱兑换、279.完全平方数、139.单词拆分、多重背包、总结
数据结构·c++·算法·leetcode·动态规划
jimy11 小时前
C语言历史版本和gnu扩展版本
c语言·算法·gnu
shehuiyuelaiyuehao1 小时前
关于算法14,15解决一些问题
算法
探序基因1 小时前
单细胞转录组Seurat去批次-FastMNN算法及大细胞量评测
linux·算法
阿Y加油吧1 小时前
二刷 LeetCode:300. 最长递增子序列 & 152. 乘积最大子数组 复盘笔记
笔记·算法·leetcode