LeetCode Hot 100|普通数组专题学习笔记(零基础版)

LeetCode Hot 100|普通数组专题学习笔记(零基础版)

本笔记整理三道题:

  1. LeetCode 53:最大子数组和------动态规划
  2. LeetCode 56:合并区间------排序 + 一次遍历
  3. LeetCode 189:轮转数组------双指针 + 三次反转

学习建议:先理解题意,再看暴力思路,最后学习最优解。不要只背代码,要能解释每一行的作用。


第一题:最大子数组和

题号:LeetCode 53

题目链接:https://leetcode.cn/problems/maximum-subarray/

1. 题目理解

给定整数数组,找到一个非空、连续的子数组,使元素和最大,返回最大和。

python 复制代码
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

最大和来自 [4, -1, 2, 1]:

text 复制代码
4 + (-1) + 2 + 1 = 6

答案是 6。子数组必须连续,而且不能为空。

2. 暴力解法

思路: 1. 外层循环选择子数组左端点 left。 2. 内层循环选择右端点

right。 3. 用 total 累加当前子数组的和。 4. 每次累加后更新最大和。

python 复制代码
class Solution:
    def maxSubArray(self, nums: list[int]) -> int:
        n = len(nums)
        max_sum = nums[0]

        for left in range(n):
            total = 0

            for right in range(left, n):
                total += nums[right]
                max_sum = max(max_sum, total)

        return max_sum

逐行理解

  • max_sum = nums[0]:不要初始化为 0。数组可能全是负数,例如
    [-4, -2, -7],正确答案是 -2。
  • for left in range(n):枚举每一个起点。
  • total = 0:每换一个起点,都要重新累计。
  • for right in range(left, n):让右端点逐渐向右扩展。
  • total += nums[right]:只加上新进入区间的数字,不必每次重新计算整个区间。
  • max_sum = max(max_sum, total):保留历史最大和。

复杂度: - 时间:O(n²),需要枚举所有连续子数组。 -

额外空间:O(1),只使用固定数量的变量。

3. 动态规划最优解

定义状态

dp[i] 表示:必须以 nums[i] 结尾的最大连续子数组和。

当前位置只有两种选择:

  1. 从当前数字重新开始:nums[i]
  2. 接在前一个位置的最优子数组后面:dp[i - 1] + nums[i]

所以状态转移公式是:

python 复制代码
dp[i] = max(nums[i], dp[i - 1] + nums[i])

例如 nums = [2, -1, 3],对应 dp = [2, 1, 4]。最后答案是

max(dp) = 4。

DP 数组版

python 复制代码
class Solution:
    def maxSubArray(self, nums: list[int]) -> int:
        n = len(nums)
        dp = [0] * n
        dp[0] = nums[0]

        for i in range(1, n):
            dp[i] = max(nums[i], dp[i - 1] + nums[i])

        return max(dp)

空间优化版(推荐掌握)

python 复制代码
class Solution:
    def maxSubArray(self, nums: list[int]) -> int:
        current_sum = nums[0]
        max_sum = nums[0]

        for i in range(1, len(nums)):
            current_sum = max(nums[i], current_sum + nums[i])
            max_sum = max(max_sum, current_sum)

        return max_sum

两个变量的区别:

  • current_sum:必须以当前位置结尾的最大连续子数组和,可能变大或变小。
  • max_sum:从开始到当前位置见过的最大和,只会保持不变或增大。

例如 [5, -10, 4]: - 看到 5:current_sum = 5,max_sum = 5 - 看到

-10:current_sum = -5,max_sum = 5 - 看到

4:current_sum = 4,max_sum = 5

复杂度: - 暴力:时间 O(n²),额外空间 O(1) - DP 数组:时间

O(n),额外空间 O(n) - 空间优化 DP:时间 O(n),额外空间 O(1)

易错点: 1. max_sum 不要初始化为 0,全负数数组会出错。 2. 更新

current_sum 时不能漏掉 + nums[i]。 3. 更新全局最大值要写

max_sum = max(max_sum, current_sum)。 4. 最后返回

max_sum,不能直接返回 current_sum。

自测:

输入 答案


[-2,1,-3,4,-1,2,1,-5,4] 6

[5,4,-1,7,8] 23

[-4,-2,-7] -2

[5,-10,4] 5


第二题:合并区间

题号:LeetCode 56

题目链接:https://leetcode.cn/problems/merge-intervals/

1. 题目理解

每个区间是 [起点, 终点]。如果区间重叠,就合并。

python 复制代码
intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]

[1, 3] 和 [2, 6] 重叠,合并成 [1, 6]。答案:

python 复制代码
[[1, 6], [8, 10], [15, 18]]

端点相接也算重叠,例如 [1, 4] 和 [4, 5] 应合并为 [1, 5]。

2. 为什么先排序?

python 复制代码
intervals.sort(key=lambda x: x[0])

x 代表一个区间,x[0]

是区间起点。按起点从小到大排序后,就能从左到右扫描。

3. 判断与合并

假设 result[-1] 是已经合并的最后一个区间,current 是当前区间。

没有重叠:

python 复制代码
current[0] > result[-1][1]

当前区间起点大于上一个区间终点,所以可以直接添加。

有重叠:

python 复制代码
result[-1][1] = max(result[-1][1], current[1])

只更新终点,并保留两个终点中的较大值。这样即使当前区间完全被已有区间包含,也不会把终点错误地改小。

4. 完整代码

python 复制代码
class Solution:
    def merge(self, intervals: list[list[int]]) -> list[list[int]]:
        intervals.sort(key=lambda x: x[0])
        result = []

        for current in intervals:
            if not result or current[0] > result[-1][1]:
                result.append(current)
            else:
                result[-1][1] = max(result[-1][1], current[1])

        return result

手算示例

输入:

python 复制代码
[[1, 3], [2, 6], [8, 10]]
  1. result 为空,添加 [1, 3]。
  2. [2, 6] 的起点 2 不大于已有终点 3,发生重叠,终点更新为
    max(3, 6) = 6。
  3. [8, 10] 的起点 8 大于已有终点 6,没有重叠,添加新区间。

结果:

python 复制代码
[[1, 6], [8, 10]]

复杂度: - 时间:O(n log n),主要由排序决定;排序后遍历是 O(n)。 -

空间:通常记为 O(n),结果列表最多保存 n

个区间,排序实现也可能使用额外空间。

易错点: 1. 比较 current[0] 和

result[-1][1],即当前起点与上一个终点。 2. 没有重叠的条件是

current[0] > result[-1][1]。 3. 合并时不能把整个 current 赋给终点。

  1. 终点更新要用 max()。 5. 使用 result[-1] 前要先检查 result

是否为空。

自测:

输入 答案


[[1,3],[2,6],[8,10],[15,18]] [[1,6],[8,10],[15,18]]

[[1,4],[2,3]] [[1,4]]

[[1,8],[2,5],[6,9]] [[1,9]]

[[1,4],[4,5]] [[1,5]]


第三题:轮转数组

题号:LeetCode 189

题目链接:https://leetcode.cn/problems/rotate-array/

1. 题目理解

把数组中的元素向右轮转 k 个位置。

python 复制代码
nums = [1, 2, 3, 4, 5, 6, 7]
k = 3

结果:

python 复制代码
[5, 6, 7, 1, 2, 3, 4]

题目要求原地修改数组,所以函数不需要返回新数组。

2. 暴力解法:每次移动一格

python 复制代码
class Solution:
    def rotate(self, nums: list[int], k: int) -> None:
        n = len(nums)
        k = k % n

        for _ in range(k):
            last = nums[-1]

            for i in range(n - 1, 0, -1):
                nums[i] = nums[i - 1]

            nums[0] = last

思路: 1. 暂存最后一个元素。 2. 从右往左,将元素向右移动一格。 3.

把暂存的元素放到下标 0。 4. 重复 k 次。

为什么从右往左?这样不会提前覆盖还没有移动的旧值。

为什么 k = k % n?数组长度为 n,轮转 n 次会回到原样。例如长度为

7,轮转 10 次等价于轮转 3 次。

复杂度: - 时间:O(nk)。 - 额外空间:O(1)。

3. 最优解:三次反转法

例子:

python 复制代码
nums = [1, 2, 3, 4, 5, 6, 7]
k = 3

第一步,反转整个数组:

text 复制代码
[1, 2, 3, 4, 5, 6, 7]
→ [7, 6, 5, 4, 3, 2, 1]

第二步,反转前 k 个元素:

text 复制代码
[7, 6, 5, 4, 3, 2, 1]
→ [5, 6, 7, 4, 3, 2, 1]

第三步,反转剩余部分:

text 复制代码
[5, 6, 7, 4, 3, 2, 1]
→ [5, 6, 7, 1, 2, 3, 4]

记忆口诀:整体反转 → 反转前 k 个 → 反转剩余部分。

4. 双指针反转函数

python 复制代码
def reverse(nums, left, right):
    while left < right:
        nums[left], nums[right] = nums[right], nums[left]
        left += 1
        right -= 1

left 指向左端,right

指向右端。交换后两个指针向中间移动,直到相遇或交错。

5. 最优解完整代码

python 复制代码
class Solution:
    def rotate(self, nums: list[int], k: int) -> None:
        n = len(nums)
        k = k % n

        def reverse(left, right):
            while left < right:
                nums[left], nums[right] = nums[right], nums[left]
                left += 1
                right -= 1

        reverse(0, n - 1)  # 整体反转
        reverse(0, k - 1)  # 反转前 k 个元素
        reverse(k, n - 1)  # 反转剩余部分

下标解释: - 整个数组:0 到 n - 1。 - 前 k 个元素:0 到

k - 1。 - 剩余部分:k 到 n - 1。

复杂度: - 时间:O(n)。 - 额外空间:O(1)。

易错点: 1. 先执行 k = k % n。 2. 前 k 个元素的右边界是

k - 1,不是 k。 3. 反转函数每次交换后要更新 left 和 right。 4.

题目要求原地修改,所以不需要 return nums。 5. LeetCode

输入数组非空;通用代码如果允许空列表,需要先处理空数组再执行取余。

自测:

输入 k 结果


[1,2,3,4,5,6,7] 3 [5,6,7,1,2,3,4]

[-1,-100,3,99] 2 [3,99,-1,-100]

[1,2,3] 4 [3,1,2]

[1,2] 0 [1,2]


三道题对比总结

题目 核心问题 核心技巧 最优时间复杂度


最大子数组和 连续子数组的最大和 动态规划 / Kadane 算法 O(n)

合并区间 合并重叠区间 排序 + 一次遍历 O(n log n)

轮转数组 向右移动元素 双指针 + 三次反转 O(n)

建议复习方法

  1. 遮住代码,先口头讲清楚思路。
  2. 自己写出暴力解法。
  3. 再独立写出最优解。
  4. 用边界用例测试,特别关注全负数、区间包含、端点相接、k
    大于数组长度等情况。
  5. 能不看答案写出来,并解释关键行,才算真正掌握。

学算法不是记住一段代码,而是遇到类似问题时,能判断该使用什么思路,以及为什么。

相关推荐
2601_965384261 小时前
从微生态与流体力学视角看:“口水臭”的系统级成因与清洁边界
经验分享·笔记
用户老李1 小时前
反向传播计算梯度举例
算法
niyongsheng1 小时前
多智能体连续时间内路径规划:AA-SIPP(m) 的 Rust 实现与算法
算法·rust·机器人
卿卿的产品经理日记2 小时前
【AI产品经理实战】Day 38|try-except 踩坑实录:报错是数据
人工智能·笔记·python·学习·产品经理
302wanger2 小时前
DeepSeek-V4.1-Flash-1017 免费用到1024,速来
算法
lisw052 小时前
微分与积分的本质区别解析
算法·数学建模·极限编程
xys_6783 小时前
深挖数据结构:树和二叉树(上)
数据结构·算法
Interview Aid1123 小时前
Google SWE Intern VO 面经:两轮 Coding,Follow-up 考察 BFS 状态
算法
琳达爱分享4 小时前
从单机令牌桶到集群限流:一次配额超发的复盘
后端·算法