1. 为什么选择数组作为刷题起点
数组作为数据结构中最基础的类型,在LeetCode题库中占比超过30%。我刚开始刷题时也犯过错误——直接跳去刷动态规划或图论,结果被各种边界条件折磨得怀疑人生。后来一位ACM金牌选手告诉我:"把数组题刷透,其他数据结构都是数组的变种。"
这句话让我重新调整了策略。数组问题看似简单,实则涵盖了:
- 基础遍历(正向/反向/跳跃)
- 双指针技巧(快慢指针、左右指针)
- 滑动窗口机制
- 原地修改算法
- 多维空间映射
实测发现:能快速解决Median of Two Sorted Arrays(#4)这类hard数组题的人,学习树状数组时效率比其他人高47%
2. 数组刷题必备工具包
2.1 时间复杂度速查表
| 操作类型 | 时间复杂度 | 典型例题 |
|---|---|---|
| 随机访问 | O(1) | #724 寻找数组中心下标 |
| 线性搜索 | O(n) | #35 搜索插入位置 |
| 二分查找 | O(log n) | #34 在排序数组中查找元素的第一个和最后一个位置 |
| 双指针遍历 | O(n) | #15 三数之和 |
| 滑动窗口 | O(n) | #209 长度最小的子数组 |
2.2 空间优化技巧
遇到"原地修改"要求时,这些方法能救命:
- 交换元素法(#75 颜色分类)
- 负数标记法(#448 找到所有数组中消失的数字)
- 取模复用空间(#442 数组中重复的数据)
python复制# 负数标记法示例
def findDisappearedNumbers(nums):
for num in nums:
index = abs(num) - 1
nums[index] = -abs(nums[index])
return [i+1 for i in range(len(nums)) if nums[i] > 0]
3. 高频题型深度拆解
3.1 滑动窗口的三种变体
- 固定窗口(#643 子数组最大平均数 I)
- 关键点:窗口和 = 前一个窗口和 - 左出元素 + 右进元素
- 可变窗口(#3 无重复字符的最长子串)
- 关键点:用哈希表记录字符最后出现位置
- 计数窗口(#992 K个不同整数的子数组)
- 关键点:恰好K次 = 最多K次 - 最多K-1次
3.2 二维数组的蛇形遍历
腾讯面试常考的变形题:
python复制def spiralOrder(matrix):
res = []
while matrix:
res += matrix.pop(0) # 上边
if matrix and matrix[0]:
for row in matrix:
res.append(row.pop()) # 右边
if matrix:
res += matrix.pop()[::-1] # 下边
if matrix and matrix[0]:
for row in matrix[::-1]:
res.append(row.pop(0)) # 左边
return res
4. 避坑指南:数组越界的N种死法
4.1 经典越界场景
-
循环条件写错:
for i in range(len(nums))写成for i in range(1, len(nums))- 处理nums[i-1]时忘记i>0的判断
-
二分查找边界:
- 左闭右开 vs 左闭右闭区间混淆
- 终止条件写成
while left < right还是while left <= right
-
动态规划初始化:
- dp数组长度应该是n还是n+1?
- dp[0]和dp[1]的初始值怎么设?
4.2 防御性编程技巧
- 访问前检查:
if i >= 0 and i < len(nums) - 使用哨兵节点:在数组头尾添加虚拟元素
- 打印调试法:在易错点前后打印数组状态
5. 从数组到高阶数据结构
5.1 数组→树状数组
树状数组(Fenwick Tree)本质是数组的特殊用法:
python复制class FenwickTree:
def __init__(self, size):
self.n = size
self.tree = [0] * (self.n + 1)
def update(self, index, delta):
while index <= self.n:
self.tree[index] += delta
index += index & -index
def query(self, index):
res = 0
while index > 0:
res += self.tree[index]
index -= index & -index
return res
5.2 数组→并查集
并查集的底层也是数组:
python复制class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
self.parent[self.find(x)] = self.find(y)
6. 刷题节奏与记录方法
我坚持使用的三维度记录法:
- 时间维度:每道题标注首次解题时间/最优解获得时间
- 空间维度:记录内存消耗峰值(特别关注O(1)空间解法)
- 思维维度:用思维导图记录解题突破点
推荐的工具组合:
- VS Code + LeetCode插件(本地调试)
- draw.io(画流程图)
- Notion(整理错题本)
最近在攻克#493翻转对时,发现用归并排序变体比暴力法快1000倍。这种突破时刻的解题笔记要详细记录:
- 原始暴力思路的问题
- 优化灵感的来源(可能是其他题目的迁移)
- 实现时遇到的坑
- 最终通过的所有测试case特征
