从思维到落地
刷 LeetCode 不是为了背题,而是训练一种能力------把模糊的问题拆解成清晰的步骤,再用最合适的工具把它实现出来。本文结合算法思维与 Python 特性,帮你建立一套"想得清楚、写得出来、跑得够快"的刷题方法论。
1. 算法思维:七种武器
LeetCode 或者面试题目千变万化,但背后的思维方式可以归纳为七类。掌握这些"武器",你就能应对绝大多数问题。
1.1. 空间换时间:哈希表的艺术
核心洞察:计算机最擅长的事情是"查表",而不是"逐个比对"。
python
# 两数之和:O(n²) 暴力解法 → O(n) 哈希解法
def two_sum(nums, target):
seen = {} # 值 → 索引的映射
for i, num in enumerate(nums):
complement = target - num
if complement in seen: # O(1) 查找
return [seen[complement], i]
seen[num] = i
return []
适用场景:需要频繁查找、统计频率、判断存在性的地方,第一时间想到哈希表。
1.2. 双指针与滑动窗口:让遍历不再暴力
核心洞察:很多遍历问题不需要两层循环,两个指针协同移动就能覆盖所有有效情况。
python
# 无重复字符的最长子串:滑动窗口
def longest_substring(s):
window = set()
left = max_len = 0
for right in range(len(s)):
while s[right] in window: # 收缩窗口
window.remove(s[left])
left += 1
window.add(s[right])
max_len = max(max_len, right - left + 1)
return max_len
变体区分:
- 左右指针:从两端向中间逼近(反转、盛水容器)
- 快慢指针:处理链表环、数组原地修改
- 滑动窗口:寻找满足条件的连续子区间
1.3. 递归、回溯与分治:化繁为简
核心洞察:大问题如果和小问题是同构的,就递归;如果需要在过程中"试错并回退",就回溯。
python
# 全排列:回溯 = DFS + 状态重置
def permute(nums):
def backtrack(path, used):
if len(path) == len(nums):
res.append(path[:])
return
for i in range(len(nums)):
if not used[i]:
used[i] = True
path.append(nums[i])
backtrack(path, used)
path.pop() # 撤销选择
used[i] = False # 状态重置
res = []
backtrack([], [False] * len(nums))
return res
1.4. DFS 与 BFS:搜索的两面
| 维度 | DFS | BFS |
|---|---|---|
| 实现方式 | 递归 / 栈 | 队列 |
| 特点 | 一条路走到底 | 一层一层扩散 |
| 适用场景 | 所有路径、连通性 | 最短路径(无权图) |
python
from collections import deque
# BFS 层序遍历:用 deque 保证 O(1) 的头尾操作
def level_order(root):
if not root: return []
q = deque([root])
result = []
while q:
level = []
for _ in range(len(q)):
node = q.popleft()
level.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
result.append(level)
return result
1.5. 动态规划:记忆化 + 状态转移
核心洞察:如果一个问题可以被拆成重叠子问题,并且子问题的解可以复用,那就用 DP。
四步法:定义状态 → 找转移方程 → 初始化 → 确定遍历顺序
python
# 打家劫舍:不相邻元素的最大和
def rob(nums):
prev, curr = 0, 0
for num in nums:
prev, curr = curr, max(curr, prev + num)
return curr
1.6. 贪心算法:每一步都选当前最优
核心洞察:如果局部最优能推导出全局最优,就不需要穷举所有可能性。
python
# 分发饼干:用最小的饼干满足最小胃口的孩子
def find_content_children(g, s):
g.sort()
s.sort()
i = j = 0
while i < len(g) and j < len(s):
if s[j] >= g[i]:
i += 1
j += 1
return i
1.7. 二分查找:有序世界的减半魔法
核心洞察:只要数据具备"单调性",就可以用二分将 O(n) 降到 O(log n)。
python
def binary_search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
2. Python 内置工具的效率密码
算法思维决定了"怎么做",而 Python 内置数据结构的特性决定了"做得快不快"。很多 LeetCode 超时(TLE),不是算法错了,而是数据结构用错了。
2.1 时间复杂度速查
| 数据结构 | 操作 | 复杂度 | 备注 |
|---|---|---|---|
| list | 索引、尾部 append/pop | O(1) | 放心用 |
| list | 头部 insert/pop、in 查找 | O(n) | ⚠️ 慎用 |
| dict/set | 增删查 | O(1) 均摊 | LeetCode 首选 |
| deque | 头尾 append/pop | O(1) | BFS 必备 |
| str | 拼接(+) | O(n) | 循环中拼接是 O(n²) |
2.2 五个必须避开的性能陷阱
陷阱 1:在 list 头部频繁增删
python
# ❌ O(n²)
nums = []
for i in range(10000):
nums.insert(0, i)
# ✅ O(n)
nums = list(range(9999, -1, -1))
陷阱 2:循环中用 += 拼接字符串
python
# ❌ O(n²)
s = ""
for i in range(10000):
s += str(i)
# ✅ O(n)
s = "".join(str(i) for i in range(10000))
陷阱 3:在 list 中用 in 频繁查找
python
# ❌ O(n × m)
if x in big_list: # 每次都遍历
# ✅ O(m)
big_set = set(big_list)
if x in big_set: # O(1)
陷阱 4:遍历 list 时直接删除元素
python
# ❌ 逻辑错误 + O(n²)
for x in lst:
if condition(x):
lst.remove(x)
# ✅ O(n)
lst = [x for x in lst if not condition(x)]
陷阱 5:用 list 做 BFS 队列
python
# ❌ list.pop(0) 是 O(n)
queue = []
queue.pop(0)
# ✅ deque.popleft() 是 O(1)
from collections import deque
queue = deque()
queue.popleft()
2.3 五个可以放心用的效率神器
| 工具 | 用途 | 优势 |
|---|---|---|
collections.Counter |
计数统计 | O(n) 构建,O(1) 查询 |
collections.defaultdict |
自动初始化 | 省去 if key not in dict 判断 |
collections.deque |
双端队列 | 头尾操作 O(1) |
heapq |
优先队列 | push/pop O(log n) |
| 列表推导式 | 构造列表 | 底层 C 实现,比 for+append 快 |
python
from collections import Counter, defaultdict, deque
import heapq
# Counter:一行搞定词频统计
freq = Counter("abracadabra")
# defaultdict:图遍历不再需要判断 key 是否存在
graph = defaultdict(list)
graph['A'].append('B')
# heapq:Top K 问题
nums = [3, 1, 4, 1, 5, 9]
heapq.heapify(nums) # 原地建堆
smallest = heapq.heappop(nums)
3. 思维与工具的协同:一个完整案例
以 LeetCode 3(无重复字符的最长子串)为例,展示思维方式和 Python 工具如何配合:
python
def length_of_longest_substring(s):
char_set = set() # 哈希集合:O(1) 查找
left = max_len = 0
for right in range(len(s)):
while s[right] in char_set: # 滑动窗口收缩
char_set.remove(s[left])
left += 1
char_set.add(s[right]) # 窗口扩张
max_len = max(max_len, right - left + 1)
return max_len
拆解:
- 算法思维:滑动窗口(双指针的变体)
- Python 工具 :
set提供 O(1) 的成员检查,避免了 O(n) 的 list 查找 - 结果:整体 O(n) 时间,O(min(n, charset)) 空间
4. 训练路径建议
- 先学套路,再刷题:每种思维方式先理解框架,再集中刷 5-10 道同类题
- 刻意练习 Pythonic 写法 :用
set替代list的in,用deque替代list做队列,用Counter替代手动计数 - 五遍刷题法:第一遍硬刚 → 第二遍优化 → 第三遍换思路 → 第四遍限时 → 第五遍融会贯通
- 每题三问 :
- 时间复杂度能不能再降?
- 有没有更合适的数据结构?
- 代码能不能更 Pythonic?
写在最后
LeetCode 训练的本质,是训练你的大脑在"问题 → 抽象 → 数据结构 → 算法 → 代码"这条链路上快速运转。Python 给了我们一把锋利的刀,但刀法(算法思维)才是决定胜负的关键。
记住三句话:
- 查得快,用哈希
- 头尾操作,用 deque
- 字符串拼接,用 join
把思维方式内化为直觉,把 Python 工具内化为肌肉记忆,刷题就不再是痛苦,而是一种享受------享受那种"问题被优雅解决"的快感。
我们一起加油,让刀法更加朴实锋利!