LeetCode 热题 100——普通数组——最大子数组和

作者:做怪小疯子日期:2025/11/23

14.最大子数组和

题目描述

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:

1 <= nums.length <= 105
-104 <= nums[i] <= 104

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

求解

(1)暴力
首先想到的基本都是暴力,该题的核心思路就是遍历所有的 nums[i…j]的和,保留最大值

1var maxSubArray = function(nums) {
2    // 暴力求解
3    let ans = -Infinity
4    // 穷举各种子数组nums[i...j]
5    for (let i = 0; i < nums.length; i++) {
6        for (let j = i; j < nums.length; j++) {
7            let sum = 0
8            for (let k = i; k <= j; k++) {
9                sum += nums[k]
10            }
11            ans = Math.max(ans, sum)
12        }
13    }
14    return ans
15};
16

有 10个用例超出时间限制
(2)暴力优化

1var maxSubArray = function(nums) {
2    // 暴力求解 —— 优化
3    let ans = -Infinity
4    for (let i = 0; i < nums.length; i++) {
5        let sum = 0
6        for (let j = i; j < nums.length; j++) {
7            // 内循环 每次都算出 i...i, i...n - 1 的值
8            sum += nums[j]
9            ans = Math.max(ans, sum)
10        }
11    }
12    return ans
13};
14

还是有六个超出了时间限制。。

(3)继续优化

本题主要使用动态规划,已经不记得动态规划是怎么回事了,先复习一波。。。
一、核心定义

动态规划是一种高效解决具有重叠子问题和最优子结构的复杂问题的算法思想。核心逻辑是:将大问题分解为若干个相互关联的小问题,通过求解小问题并缓存结果(避免重复计算),最终从子问题的解中构造出原问题的最优解。

二、两大核心前提(问题适用条件)

动态规划仅适用于满足以下两个条件的问题:

  1. 重叠子问题(Overlapping Subproblems)
    大问题可分解为多个小问题,且这些小问题在求解过程中会被反复计算(而非相互独立)。
    示例:斐波那契数列中,fib(5) = fib(4) + fib(3),而 fib(4) = fib(3) + fib(2),fib(3) 被重复计算。
  2. 最优子结构(Optimal Substructure)
    大问题的最优解可以由其子问题的最优解推导得出。
    即:若一个问题的最优解包含了子问题的解,那么这些子问题的解也必须是最优的。
    示例:从 A 到 C 的最短路径若经过 B,则 A→B 和 B→C 的路径也必须是各自的最短路径。

三、关键概念

  1. 状态(State)
    对问题某一阶段的快照描述,通常用一个或一组变量表示(如 dp[i]、dp[i][j])。
    作用:概括当前问题的进展,足以推导下一步状态。
    示例:dp[i] 可表示 “爬到第 i 阶楼梯的方法数”“以第 i 个元素结尾的最大子数组和”。
  2. 状态转移方程(State Transition Equation)
    动态规划的核心灵魂,定义了 “如何从先前的状态计算当前状态”。
    形式:dp[i] = f(dp[i-1], dp[i-2], …)(由子问题的解推导当前解)。
    示例:爬楼梯中 dp[i] = dp[i-1] + dp[i-2](从 i-1 阶爬 1 步,或从 i-2 阶爬 2 步)。
  3. DP 表(DP Table)
    存储子问题解的数据结构(通常是数组或哈希表),用于缓存结果、避免重复计算。
    维度:根据状态定义确定(一维、二维或多维)。
    示例:一维 dp[] 解决爬楼梯、最大子数组和;二维 dp[i][j] 解决最长公共子序列(LCS)。
  4. 初始化(Initialization)
    确定 DP 表的初始值(最小子问题的解),是状态转移的起点。
    示例:爬楼梯中 dp[1] = 1、dp[2] = 2;最大子数组和中 dp[0] = nums[0]。
  5. 计算顺序(Order of Calculation)
    确定填充 DP 表的顺序,确保计算 dp[i] 时,所有依赖的子问题(如 dp[i-1])已被求解。
    常见顺序:从左到右、从上到下(自底向上)。

四、解题步骤(通用流程)

  • 定义状态:明确 dp[i] 或 dp[i][j] 的具体含义(最关键步骤)。
  • 推导状态转移方程:找出当前状态与子状态的关系。
  • 初始化 DP 表:设置最小子问题的初始值。
  • 按顺序填充 DP 表:确保依赖的子状态已计算完成。
  • 返回结果:从 DP 表中提取原问题的解(可能是 dp[n]、max(dp[]) 等)。

五、两种实现方式

  1. 自底向上(Bottom-Up)- 迭代法
    思路:从最小子问题开始,逐步向上求解,直到解决原问题。
    实现:使用 DP 表(数组 / 哈希表)迭代填充。
    优点:无递归开销,效率高,无栈溢出风险。
    示例:爬楼梯、最大子数组和的迭代解法。
  2. 自顶向下(Top-Down)- 递归 + 记忆化(Memoization)
    思路:从原问题出发,递归分解为子问题;用 “备忘录”(哈希表 / 数组)缓存已求解的子问题,避免重复计算。
    实现:递归函数 + 备忘录(如 memo 对象)。
    优点:逻辑直观,贴近问题的递归描述。
    缺点:存在递归调用开销,可能栈溢出(适用于子问题数量较少的场景)。
    示例(爬楼梯):
1function climbStairs(n, memo = {}) {  
2	if (n in memo) return memo[n]; // 缓存命中,直接返回  
3	if (n === 1) return 1;  
4	if (n === 2) return 2;  
5	memo[n] = climbStairs(n-1, memo) + climbStairs(n-2, memo); // 递归求解并缓存  
6	return memo[n];  
7}  

六、经典例题解析(DP 模型)

问题类型状态定义状态转移方程初始化条件结果提取方式
爬楼梯dp[i]:爬到第 i 阶楼梯的方法数dp[i] = dp[i-1] + dp[i-2]dp[1] = 1,dp[2] = 2dp[n](n为目标阶数)
最大子数组和dp[i]:以第 i 个元素结尾的最大子数组和dp[i] = max(nums[i], dp[i-1] + nums[i])dp[0] = nums[0](nums为输入数组)max(dp)(整个dp数组的最大值)
最长递增子序列(LIS)dp[i]:以 nums[i] 结尾的最长递增子序列长度dp[i] = max(dp[j]+1, dp[i])(满足 j < i 且 nums[j] < nums[i])dp[i] = 1(所有元素初始值为1,单个元素本身是长度为1的子序列)max(dp)(整个dp数组的最大值)
最长公共子序列(LCS)dp[i][j]:s1[0..i-1] 与 s2[0..j-1] 的LCS长度若 s1[i-1] == s2[j-1]:dp[i][j] = dp[i-1][j-1] + 1;否则:dp[i][j] = max(dp[i-1][j], dp[i][j-1])dp[0][j] = 0(任意j),dp[i][0] = 0(任意i)dp[m][n](m为s1长度,n为s2长度)

应用场景:
动态规划主要用于解决以下三类问题:
优化问题:求最大 / 最小、最长 / 最短(如最大子数组和、最短路径)。
计数问题:求方案数、组合数(如爬楼梯、不同路径)。
字符串问题:子序列、子串匹配(如 LCS、最长回文子串)。

  • 最大子数组和思路:

定义状态 (DP State):我们定义 dp[i] 表示以数组中第 i 个元素 结尾 的连续子数组的最大和。
例如,对于数组 [-2, 1, -3, 4, -1, 2, 1, -5, 4]:
dp[0] 表示以 -2 结尾的最大子数组和,它就是 -2 本身。
dp[1] 表示以 1 结尾的最大子数组和,它可以是 1 或者 -2 + 1,取最大值 1。
dp[2] 表示以 -3 结尾的最大子数组和,它可以是 -3 或者 1 + (-3),取最大值 -2。
dp[3] 表示以 4 结尾的最大子数组和,它可以是 4 或者 -2 + 4,取最大值 4。
以此类推…
确定状态转移方程 (State Transition):对于第 i 个元素,我们有两种选择:a. 只取第 i 个元素本身,作为一个新的子数组的起点。b. 将第 i 个元素加入到以第 i-1 个元素结尾的子数组中。
因此,状态转移方程为:dp[i] = max(nums[i], dp[i-1] + nums[i])
nums[i] 对应选择 a。
dp[i-1] + nums[i] 对应选择 b。
我们取两者中的较大值作为 dp[i] 的值。
初始化 (Initialization):dp[0] = nums[0],因为以第一个元素结尾的子数组只有它自己。
最终结果 (Result Extraction):我们需要遍历整个 dp 数组,找到其中的最大值。这个最大值就是所有可能的连续子数组中的最大和。因为 dp[i] 只代表以 i 结尾的最大子数组和,整个数组的最大子数组可能在任意位置结束。
result = max(dp[0…n-1])

代码实现:

1var maxSubArray = function(nums) {
2    if (nums.length === 1) return nums[0]
3    // 动态规划 
4    let dp = new Array(nums.length)
5    dp[0] = nums[0]
6    let ans = -Infinity // 当nums长度为1的时候,不会进入循环,存在边界问题
7    // let ans = dp[0]
8    // dp[i]代表的是 截至当前位置i的子数组的 最大子数组和
9    // i位置需要考虑:i本身  i + (i - 1)之前的子数组和
10    for (let i = 1; i < nums.length; i++) {
11        dp[i] = Math.max(nums[i], dp[i - 1] + nums[i])
12        ans = Math.max(ans, dp[i])
13    }
14    return ans
15};
16

可以一起进行判断:

1// 使用动态规划
2var maxSubArray = function(nums) {
3    // 动态规划 
4    let dp = new Array(nums.length)
5    dp[0] = nums[0]
6    ans = dp[0]
7    for (let i = 1; i < nums.length; i++) {
8        dp[i] = Math.max(nums[i], dp[i - 1] + nums[i])
9        ans = Math.max(ans, dp[i])
10    }
11    return ans
12};
13

正好学习了动态规划,把 爬楼梯也做了

15. 爬楼梯

题目描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶
    示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

提示:
1 <= n <= 45

求解

dp[i]:爬到第 i 阶楼梯的方法数
状态转移方程:dp[i] = dp[i-1] + dp[i-2]
初始化状态:dp[1] = 1,dp[2] = 2

代码:

1var climbStairs = function(n) {
2    if (n === 1) return 1
3    if (n === 2) return 2
4    let n1 = 1, n2 = 2
5    let ans = 0
6    for (let i = 3; i <= n; i++) {
7        ans = n1 + n2
8        n1 = n2
9        n2 = ans
10    }
11    return ans
12};
13

也可以使用递归

有点事情忙了好几天,断了断了,今天开始补。加油加油!


LeetCode 热题 100——普通数组——最大子数组和》 是转载文章,点击查看原文


相关推荐


全脑智能“破局者”诞生,黑芝麻智能SesameX™,重构具身智能新范式
高工智能汽车2025/11/22

当前,全球人工智能技术正在加速向“物理世界赋能”,智能汽车与机器人产业技术融合进入深水区,一场关乎“全脑智能”的产业革命已经悄然来临。 11月20日,全球车规级高性能AI芯片厂商黑芝麻智能举行「多维进化 智赋新生」机器人平台产品发布会,并发布多维具身智能计算平台——黑芝麻智能SesameX™,包含一整套从端侧模组到全脑智能的体系化计算平台。 据了解,黑芝麻智能SesameX多维具身智能计算平台是行业唯一符合车规安全的具身智能计算平台,亦是行业首个针对具身智能商业化部署的全栈计算平台,包含商用


MySQL中的字符集与排序规则
程序新视界2025/11/20

在MySQL中,当使用字符串类型时,有两方面的知识我们需要特别关注一下:字符集和排序规则。如果使用不当,则可能会导致性能问题或在插入数据时出现一些异常情况。 字符集定义了对应列允许使用的字符,而排序规则是用于比较这些字符的基础规则。通常,每个类型的字符集都会有多种排序规则,但一个排序规则只能属于一个字符集。 这篇文章,我们就围绕MySQL中字符集以及排序规则展开,详细聊聊相关的技术点。 MySQL中的字符集 MySQL支持广泛的字符集,包括GB2312、GBK、BIG5等本地字符集,以及多种Un


基于 Kafka 与时间轮实现高性能延迟消息
master_hl2025/11/19

1.整体链路 Kafka → RocksDB → SystemTimer → TimingWheel → Kafka public void sendDelay(long delayMs, String topic, String message) { try { DelayMessage delayMessage = new DelayMessage(delayMs, topic, message); rocksDB.put(delayMessage.g


Python 的内置函数 sum
IMPYLH2025/11/17

Python 内建函数列表 > Python 的内置函数 sum Python 的内置函数 sum() 是一个用于计算可迭代对象中所有元素之和的高效工具。这个函数可以接受一个可迭代对象(如列表、元组、集合等)作为参数,并返回其中所有元素的总和。 基本用法 numbers = [1, 2, 3, 4, 5] total = sum(numbers) # 返回 15 可选参数 sum() 函数还接受一个可选的第二个参数 start,用于指定求和的初始值。默认情况下 start 为 0。


ios包体积管理方案
denggun123452025/11/16

iOS 包体积优化是一个系统性的工程,需要从代码、资源、第三方库、构建配置等多个维度进行综合管理。下面我将梳理一个全面的 iOS 包体积管理方案。 一、包体积分析 在进行任何优化之前,必须先了解 App 体积到底由什么构成。 使用 Xcode 的 App Thinning Size Report 操作:Archive -> Distribute App -> App Store Connect -> 选择 Ad Hoc 或 App Store -> Next -> 在 "App T


Bash 入门
hubenchang05152025/11/15

#Bash 入门 #Hello World Bash 的内置命令 echo 可以打印文本。例如: $ echo Hello World Hello World echo 命令的 -e 选项激活转义字符的解释。例如: $ echo -e "Hello \n World" Hello World #命令格式 Bash 命令基本遵循以下格式: 命令 参数1 参数2 参数3 ... 例如在 echo Hello World 中,echo 是命令,Hello 是参数1,World 是参数2。 而


Python 的内置函数 issubclass
IMPYLH2025/11/14

Python 内建函数列表 > Python 的内置函数 issubclass Python 的内置函数 issubclass 用于检查一个类是否是另一个类的子类(直接或间接继承)。它是 Python 面向对象编程中类型检查的重要工具。 语法 issubclass(class, classinfo) 参数说明 class:需要检查的类(必须是类对象,不能是实例)classinfo:可以是一个类对象,或者由类对象组成的元组 返回值 返回布尔值: True:如果 class 是 c


Flutter 3.38 发布,快来看看有什么更新吧
恋猫de小郭2025/11/13

在 11 月 13 日的 FlutterFlightPlans 直播中,Flutter 3.38 如期而至,本次版本主要涉及 Dot shorthands、Web 支持增强、性能改进、问题修复和控件预览等方面。 Dot shorthands 在 Dart 3.10 + Flutter 3.38 中开始默认支持 Dot shorthands ,通过 Dot shorthands 可以使用简写方式省略类型前缀,例如使用 .start 而不是 MainAxisAlignment.start : /


HTML 的 <canvas> 标签
hubenchang05152025/11/11

#HTML 的 <canvas> 标签 请查看 HTML 元素帮助手册 了解更多 HTML 元素。 <canvas> 元素可被用来通过 JavaScript(Canvas API 或 WebGL API)绘制图形及图形动画。 #属性 请查看 HTML 元素的全局属性 了解 HTML 元素的全局属性。 height: 该元素占用空间的高度,以 CSS 像素(px)表示,默认为 150。 moz-opaque(废弃): 通过设置这个属性,来控制 canvas 元素是否半透明。如果你不想 c


CCState:为大型 Web 应用设计的状态管理库
温宇飞2025/11/9

CCState 是一个基于 Signal 的状态管理库。它通过三种语义化的信号类型(State、Computed、Command)实现读写能力隔离,并原生支持 async/await 的异步计算,让状态管理变得简单直观。CCState 与框架无关,可与 React、Vue、Solid.js 等任何 UI 框架无缝集成。它在 秒多 等项目中得到验证,为大规模应用而设计。 快速上手 Signal Signal 是一个轻量级的描述对象,它本身不存储值,只是一个"引用"或"标识符"。所有 Signal

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读