大家好,我是吴师兄。
在 LeetCode 上刷题时,很多题目看似复杂,但实际上,许多问题可以归纳为几种常见的算法模式。
如果你掌握了这些模式,就能高效地解决大量问题
。
以下是 8 个常见的模式和它们在 LeetCode 中的应用案例,学会这些模式,你就能轻松应对 80% 的 LeetCode 问题。
1、滑动窗口:优化子数组和子字符串问题
滑动窗口是一种常用的技巧,特别适合解决子数组和子字符串相关的问题。滑动窗口的核心思想是在一个可变大小的窗口中维护一些信息,并通过窗口的移动来缩小问题的范围。
一般来说,可以通过三问三答的形式进行思考:
1、对于每一个右指针 right 所指的元素 ch ,做什么操作?
2、什么时候要令左指针 left 右移?left对应的元素做什么操作?【循环不变量】
3、什么时候进行ans的更新?
这三个问题,本质上对应了滑窗问题需要考虑的三个基本要素:
1、right 指针以及所对应的元素 ch
2、left 指针以及所对应的元素 left_ch
3、ans 答案变量
以 LeetCode 3.
无重复字符的最长子串
为例,题目给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
class Solution:
def lengthOfLongestSubstring(self,s: str) -> int:
ans = 0
hash_set = set()
left = 0
for right, ch in enumerate(s) :
# Q1:对于每一个右指针 right 所指的元素ch,做什么操作?
# A1:若 ch 不在哈希集合中,将ch 加入哈希集合
if ch not in hash_set:
hash_set.add(ch)
#Q3:什么时候进行ans的更新?
# A3:若 ch 不在哈希集合中,ans 更新
ans = max(ans,right-left+1)
else:
# Q2:什么时候要令左指针 left 右移?
# left对应的元素做什么操作?【循环不变量】
# A2: ch 在哈希集合中,left右移直到 ch 不在哈希集合中
while(ch in hash_set):
left_ch = s[left]
hash_set.remove(left_ch)
left += 1
# A1:若 ch 在哈希集合中,left 右移完毕后,将ch 加入哈希集
hash_set.add(ch)
return ans
2、二分查找:寻找分割点
二分查找是一种高效的查找方法,特别适合用于有序数组或具有一定规律的数据结构中。二分查找的核心思想是每次将搜索区间缩小一半,以此快速逼近答案。
一般来说,可以通过三问三答的形式进行思考:
-
-
如何调整左右指针 left 和 right 以缩小搜索范围?
-
这三个问题,本质上对应了二分查找问题需要考虑的三个基本要素:
-
-
-
以 LeetCode 34.
在排序数组中查找元素的第一个和最后一个位置
为例,题目要求在一个升序排列的数组中,找到一个目标值出现的起始和结束位置。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def findFirst(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
# Q1:对于每个中间点 mid,我们应该如何处理?
# A1:如果 mid_val 大于等于目标值,右边界收缩
if nums[mid] >= target:
right = mid - 1
else:
left = mid + 1
# Q3:返回条件
# A3:返回左指针,代表第一个满足条件的位置
return left
def findLast(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
# A1:如果 mid_val 小于等于目标值,左边界收缩
if nums[mid] <= target:
left = mid + 1
else:
right = mid - 1
# A3:返回右指针,代表最后一个满足条件的位置
return right
# 找第一个出现位置
first = findFirst(nums, target)
# 如果第一个位置超出范围或不等于目标值,说明目标值不存在
if first >= len(nums) or nums[first] != target:
return [-1, -1]
# 找最后一个出现位置
last = findLast(nums, target)
return
[first, last]
3、堆:寻找 top-k 元素
堆是一种适合解决 top-k 问题的数据结构。通过维护一个 k 大小的堆,可以高效地找到数组中的前 k 个最大或最小元素。
一般来说,可以通过三问三答的形式进行思考:
-
-
-
这三个问题,本质上对应了堆问题需要考虑的三个基本要素:
-
-
-
以 LeetCode 215.
数组中的第 K 个最大元素
为例,题目要求找到数组中第 k 个最大的元素。
import heapq
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
# Q1:如何初始化堆?
# A1:构建一个最小堆,初始时包含前 k 个元素
heap = nums[:k]
heapq.heapify(heap)
# Q2:如何处理新的元素以维护堆的性质?
# A2:对于剩余元素,如果大于堆顶,则替换堆顶并重新调整堆
for num in nums[k:]:
if num > heap[0]:
heapq.heapreplace(heap, num)
# Q3:如何从堆中提取最终答案?
# A3:堆顶即为第 k 大的元素
return heap[0]
4、深度优先搜索(DFS):遍历图和树的基础
深度优先搜索(DFS)是一种遍历或搜索图、树结构的算法,常用于解决全路径、连通性等问题。
一般来说,可以通过三问三答的形式进行思考:
-
-
-
这三个问题,本质上对应了 DFS 问题需要考虑的三个基本要素:
-
-
-
以 LeetCode 104.
二叉树的最大深度
为例,题目要求计算二叉树的最大深度。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
# Q1:如何处理当前节点?
# A1:如果节点为空,直接返回 0
if not root:
return 0
# Q2:如何递归到子节点?
# A2:分别递归计算左子树和右子树的深度
left_depth = self.maxDepth(root.left)
right_depth = self.maxDepth(root.right)
# Q3:如何处理递归返回后的结果?
# A3:返回左子树和右子树深度的较大者,再加 1 表示当前层级
return max(left_depth, right_depth) + 1
5、拓扑排序:任务调度与依赖
拓扑排序用于解决具有依赖关系的任务排序问题,通常在有向无环图(DAG)中应用。
一般来说,可以通过三问三答的形式进行思考:
-
-
-
这三个问题,本质上对应了拓扑排序问题需要考虑的三个基本要素:
-
-
-
以 LeetCode 207.
课程表
为例,题目要求判断是否可能完成所有课程。
from collections import deque, defaultdict
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# Q1:如何构建图和入度数组?
# A1:构建邻接表和入度数组
graph = defaultdict(list)
in_degree = [0] * numCourses
for dest, src in prerequisites:
graph[src].append(dest)
in_degree[dest] += 1
# Q2:如何处理入度为 0 的节点?
# A2:将所有入度为 0 的节点入队列,并开始遍历
queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
completed_courses = 0
while queue:
course = queue.popleft()
completed_courses += 1
# Q3:如何更新其他节点的入度?
# A3:减少邻接节点的入度,如果入度变为 0,加入队列
for neighbor in graph[course]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return completed_courses == numCourses
6、广度优先搜索(BFS):层级遍历与最短路径
广度优先搜索(BFS)是一种层级遍历算法,特别适合用于搜索最短路径或进行层级遍历的问题。
一般来说,可以通过三问三答的形式进行思考:
-
-
-
这三个问题,本质上对应了 BFS 问题需要考虑的三个基本要素:
-
-
-
以 LeetCode 102.
二叉树的层序遍历
为例,题目要求返回二叉树的层序遍历结果。
from collections import deque
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
# Q1:如何处理当前层的节点?
# A1:使用队列存储当前层的所有节点
queue = deque([root])
result = []
while queue:
level_size = len(queue)
level = []
# Q2:如何扩展到下一层的节点?
# A2:遍历当前层所有节点,将其子节点加入队列
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# Q3:搜索完成的条件
# A3:将当前层结果加入最终结果集
result.append(level)
return result
7、双指针:高效遍历与查找
双指针技巧适用于数组和字符串的遍历与查找问题,尤其是在需要高效处理对称性或前后关系时。