LeetCode Hot 100
1 Tree
1.1 Recursion
- 递归三角:
node
,node.left
,node.right
构成一个递归三角,即当前节点,左子树,右子树 - 递: 调用递归函数是用来向下递的
- 归: return是用来返回给父节点的, 比如
return root
,return value
- 边界条件: 用于终止向下递,要考虑空节点的情况,即当前节点,左子树,右子树都为空

- 先记录当前节点,再向左子树递,最后向右子树递
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
self.res = []
def dfs(node):
if node is None: return
self.res.append(node.val) # 前序位置
dfs(node.left)
dfs(node.right)
dfs(root)
return self.res
- 先向左子树递,再记录当前节点,最后向右子树递
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
self.res = []
def dfs(node):
if node is None: return
dfs(node.left)
self.res.append(node.val) # 中序位置
dfs(node.right)
dfs(root)
return self.res
- 先向左子树递,再向右子树递,最后记录当前节点
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
self.res = []
def dfs(node):
if node is None: return
dfs(node.left)
dfs(node.right)
self.res.append(node.val) # 后序位置
dfs(root)
return self.res
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root: return 0
l_depth = self.maxDepth(root.left)
r_depth = self.maxDepth(root.right)
return max(l_depth, r_depth) + 1
- 两端点一定是叶子结点,因为如果不是叶子结点,则会继续走向叶子结点

# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.ans = 0
def dfs(node):
if node is None: return -1 # 叶子结点返回0给父节点
l_len = dfs(node.left)
r_len = dfs(node.right)
self.ans = max(self.ans, l_len + r_len + 2)
return max(l_len, r_len) + 1
dfs(root)
return self.ans
- p.val和q.val是否相同

- 子问题:左子树和左子树是否相同,右子树和右子树是否相同

# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q
return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
- p.left和q.right比较,p.right和q.left比较
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q
return p.val == q.val and self.isSameTree(p.left, q.right) and self.isSameTree(p.right, q.left)
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
return self.isSameTree(root.left, root.right)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root: return
left = self.invertTree(root.left)
right = self.invertTree(root.right)
root.left, root.right = right, left
return root
1.2 LevelOrderTraversal
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
ans = []
from collections import deque
q = deque([root])
while q:
tmp = []
for _ in range(len(q)): # for loop [], until it's full
node = q.popleft()
tmp.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
ans.append(tmp)
return ans
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
ans = []
from collections import deque
q = deque([root])
while q:
tmp = []
for _ in range(len(q)): # for loop [], until it's full
node = q.popleft()
tmp.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
ans.append(tmp[::-1] if len(ans)%2 else tmp)
return ans
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
if root is None:
return
from collections import deque
q = deque([root])
tmp = []
while q:
node = q.popleft()
tmp.append(node.val)
if node.right: q.append(node.right)
if node.left: q.append(node.left)
return tmp[-1]
- 二叉树的完全性检验
- 思路:以BFS遍历,第一次遇到空节点flag=True,如果之后遇到的节点都是空节点,则为完全二叉树
- 细节: 不需要判断if node.left和if node.left,因为在下一次出队时,无论是None还是非空,都会被判断。如果Node为空,则不会再继续入队,q终将会完全出队(则此时为完全二叉树),或半路打断(则此时为非完全二叉树)。

# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isCompleteTree(self, root: Optional[TreeNode]) -> bool:
q = deque([root])
flag = False
while q:
node = q.popleft()
if not node:
flag = True
else:
if flag: return False # flag为true时遇到非空节点, 说明不是完全二叉树
q.append(node.left) # 无需判断node.left是否为空
q.append(node.right)
return True
# Time Complexity: O(n), where n is the total number of nodes in the binary tree.
# Space Complexity: O(n), because in the worst case, the queue can hold all nodes of the tree at one level, and we are storing all nodes in memory.
1.3 BST
BST(Binary Search Tree),二叉搜索树具有左子树 < Root < 右子树的性质。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
def dfs(nums, lo, hi):
if lo > hi: return
# 以升序数组的中间元素作为根节点 root
mid = lo + (hi - lo) // 2
root = TreeNode(nums[mid])
root.left = dfs(nums, lo, mid - 1)
root.right = dfs(nums, mid + 1, hi)
return root
return dfs(nums, 0, len(nums)-1)
1.4 Common Ancestor
https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/
https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/
2 Sliding Window
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
ans = 0
cnt = Counter()
left = 0
for right, c in enumerate(s): # add the new character to the s, from right
cnt[c] += 1
while cnt[c] > 1: # abcb -> bcb -> cb
cnt[s[left]] -= 1
left += 1
ans = max(ans, right - left + 1)
return ans
- 找到字符串中所有字母异位词: https://leetcode.cn/problems/find-all-anagrams-in-a-string
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
n, m, res = len(s), len(p),[]
if n < m: return res
p_cnt = [0] * 26
s_cnt = [0] * 26
for i in range(m): # initialize p_cnt
p_cnt[ord(p[i]) - ord("a")] += 1
s_cnt[ord(s[i]) - ord("a")] += 1
if s_cnt == p_cnt:
res.append(0)
for i in range(m,n): # sliding window
s_cnt[ord(s[i-m])-ord("a")] -= 1
s_cnt[ord(s[i])-ord("a")] += 1
if s_cnt == p_cnt:
res.append(i-m+1) # start index
return res
3 Prefix Sum
- 计算前缀和s,注意要初始化一个0
- 因为s[j] - s[i] = k,所以s[j] - k = s[i]
- 每次寻找 s[j] 前面有多少个前缀和等于 s[j]−k, 用哈希表记录前缀和的个数,就能 O(1) 算出前面有多少个 s[j]−k
- 遍历前缀和s, ans += cnt[s[j] - k], cnt[s[i]] += 1,顺序不能颠倒,因为如果k=0,会多记一次
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
s = [0] * (len(nums) + 1)
for i, num in enumerate(nums):
s[i+1] = s[i] + num
ans = 0
cnt = Counter()
for sj in s:
ans += cnt[sj - k]
cnt[sj] += 1
return ans
4 Stack
class Solution:
def isValid(self, s: str) -> bool:
if len(s)%2 != 0:
return False
mp = {"}":"{", ")":"(", "]":"["}
st = []
for c in s:
if c not in mp: # c是左括号
st.append(c)
elif not st or st.pop() != mp[c]: # c是右括号
return False # 没有左括号,或右括号不匹配
return not st # 所有左括号必须匹配完毕,检查左括号比右括号多的情况,如{{}
class Solution:
def decodeString(self, s: str) -> str:
def dfs(s, i):
res, multi = "", 0
while i < len(s):
if "0" <= s[i] <= "9":
multi = multi * 10 + int(s[i]) # times
elif s[i] == "[":
i, tmp = dfs(s, i+1)
res += multi * tmp
multi = 0
elif s[i] == "]":
return i, res
else:
res += s[i]
i += 1
return res # the entire string s has been processed in the end
return dfs(s, 0)
5 Linked List
5.1 反转链表
- 初始化:pre指向null,cur指向head
- 先暂存head.next,再改变head.next,最后更新head:
- tmp = cur.next
- cur.next = pre
- pre = cur
- cur = tmp
- 必记性质:反转结束后,pre指向这一段的末尾,cur指向这一段末尾的下一个节点

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
pre, cur = None, head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
92.反转链表II: https://leetcode.cn/problems/reverse-linked-list-ii/
25.K个一组的反转链表: https://leetcode.cn/problems/reverse-nodes-in-k-group/
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# 计算链表长度n
n = 0
cur = head
while cur:
n += 1
cur = cur.next
# 初始化
dummy = ListNode(next=head)
p0 = dummy
pre = None
cur = head
# k个一组处理
while n >= k:
n -= k
for _ in range(k):
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
# 连接反转片段与剩下链表的部分
tmp = p0.next
tmp.next = cur # 变向: 这一组(已反转)的最后一个节点指向下一组(未反转)的第一个节点
p0.next = pre # 重连接: 前部连接
p0 = tmp # 更新: p0更新到下一组的上一个节点
return dummy.next
5.2 快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
- 关注fast和slow的相对运动,相当于fast相对运动一格,如果有环,最终fast肯定会与slow相遇
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast is slow:
return True
return False
- 哈希表记录遍历过的节点, 记录大于2表示有环,且为入口
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
visited = Counter()
while head:
if visited[head]:
return head
visited[head] += 1
head = head.next
return None
Follow up: Can you solve it using O(1) (i.e. constant) memory?
- 最差情况: 当慢指针进入环时,快指针刚好在慢指针前面,根据相对距离分析,快指针需要走(环长-1)步的相对距离才能追上慢指针,其余任何情况快指针走的步数都小于(环长-1),所以慢指针移动的距离是小于环长的。

- slwo和fast相遇之后:head从出发点出发,slow从相遇点出发,head和slow相遇的位置必定是入口,这是因为设相遇点到入口的距离为c, a - c = (k - 1)(b + c)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast is slow: # 有环
while slow is not head:
slow = slow.next
head = head.next
return slow
return None
- 找到链表中点,反转链表

- 先暂存head.next,再改变head.next,最后更新head:
- tmp = head.next
- tmp2 = head2.next
- head.next = head2
- head2.next = tmp
- head = tmp
- head2 = tmp2

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
def reverseList(self, head):
pre = None
cur = head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
def reorderList(self, head: Optional[ListNode]) -> None:
mid = self.middleNode(head)
head2 = self.reverseList(mid)
while head2.next:
tmp = head.next
tmp2 = head2.next
head.next = head2
head2.next = tmp
head = tmp
head2 = tmp2
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
def reverseList(self, head):
pre = None
cur = head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
def isPalindrome(self, head: Optional[ListNode]) -> bool:
if head is None:
return True
mid = self.middleNode(head)
head2 = self.reverseList(mid)
while head2:
if head.val != head2.val:
return False
head = head.next
head2 = head2.next
return True
5.3 删除链表
5.4 前后指针
- 初始化: A指向headA,B指向headB,同时开始向前遍历
- 遍历链表: a + (b - c) = b + (a - c)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
A = headA
B = headB
while A!=B:
A = A.next if A else headB
B = B.next if B else headA
return A
6 Two Pointers

6.1 相向双指针
- 面积等于高度*宽度
- 容器的高度取决于短线,容器的宽度取决于这两条线的距离(下标的差)

- 前缀和后缀隔板中的短板 - 当前高度 = 可蓄水量

class Solution:
def trap(self, height: List[int]) -> int:
left, right = 0, len(height) - 1
pre_max, sur_max = 0, 0
res = 0
while left < right:
sur_max = max(sur_max, height[right])
pre_max = max(pre_max, height[left])
if sur_max > pre_max:
res += pre_max - height[left]
left += 1
else:
res += sur_max - height[right]
right -= 1
return res
# O(n): 每次指针移动元素是O(1),总共要移动n次
# O(1)
7 Dynamic Programing
动态规划五部曲:
- 确定dp数组以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
7.1 0-1背包
7.2 完全背包
1. 确定dp数组以及下标的含义
dp[j]: 凑齐j金额的最小硬币数
2. 确定递推公式
dp[j] = min(d[j], d[j-coin] + 1)
3. dp数组如何初始化
dp = [float("inf")] * (amount + 1),
dp[0] = 0,表示容量为0的背包,装0个
4. 确定遍历顺序
默认循环外层物品,内层背包,由于是完全背包,不需要考虑物品只能拿一次,所以从前向后遍历dp数组。
所以,coins(物品)放在外循环,target(背包)在内循环。且内循环正序。
5. 举例推导dp数组
以输入:coins = [1, 2, 5], amount = 5为例
dp = [0, inf,inf, inf,inf, inf]
加入1,考虑1,dp = [0, 1, 2, 3, 4, 5]
加入2,考虑1, 2,dp = [0, 1, 1, 2, 2, 3]
加入5,考虑1, 2, 5, dp = [0, 1, 1, 2, 2, 1]
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [float("inf")] * (amount + 1)
dp[0] = 0
for coin in coins:
for j in range(coin, amount + 1):
dp[j] = min(dp[j], dp[j - coin] + 1)
return dp[-1] if dp[-1] != float("inf") else -1 # 如果dp[-1]是inf,说明无法凑齐该金额
7.3 打家劫舍
1. 确定dp数组以及下标的含义
dp[]
2. 确定递推公式
3. dp数组如何初始化
4. 确定遍历顺序
5. 举例推导dp数组
7.4 子序列/子串
- 子序列(Subsequence): 不连续
- 子数组(Subarray)和子串(Substring):连续
1. 定义
d[i][j]: 的前i个字符和的前j个字符的LCS
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [[0] * (n+1) for _ in range(m+1)]
for i, x in enumerate(text1):
for j, y in enumerate(text2):
if x == y:
dp[i+1][j+1] = dp[i][j] + 1
else:
dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
return dp[m][n]
1. 定义
d[i][j]: 的前i个字符转变到的前j个字符的最少次数
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
# Initialize the dp array
dp = [[0] * (n+1) for _ in range(m+1)]
for i in range(m+1):
dp[i][0] = i
for j in range(n+1):
dp[0][j] = j
for i, x in enumerate(word1):
for j, y in enumerate(word2):
if x == y:
dp[i+1][j+1] = dp[i][j] # 尾部元素相同,不操作
else:
dp[i+1][j+1] = 1 + min(dp[i+1][j], dp[i][j+1], dp[i][j]) # 增删替
return dp[-1][-1]
-
哈希表
num_set
:通过哈希表存储数组中的所有元素,支持常数时间复杂度的查找操作。 -
跳过非起点的数字:通过检查
num - 1
是否存在,我们确保每个序列只被完整计算一次,避免了重复计算。 -
举例推导
我们以 nums = [100, 4, 200, 1, 3, 2] 为例:
- 将数组 nums 转换为集合 set(nums)
- 逐个遍历数组:
- 遍历 100:99 不在集合中,因此它是一个序列的起点。当前序列只有 100,长度为 1。
- 遍历 4:3 在集合中,因此跳过,不处理(因为 4 已经被包含在以 1 开始的序列中)。
- 遍历 200:199 不在集合中,因此它是一个序列的起点。当前序列只有 200,长度为 1。
- 遍历 1:0 不在集合中,因此它是一个序列的起点。检查 2、3、4 都在集合中,最长序列为 [1, 2, 3, 4],长度为 4。
最终最长序列的长度为 4。
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
if not nums:
return 0
nums = set(nums) # 去重
max_cnt = 0
for num in nums:
if num - 1 not in nums: # num没有前一个元素,说明cur是作为连续序列的第一个元素
cur = num
cnt = 1
while cur + 1 in nums: # check the consecutive sequence
cur += 1
cnt += 1
max_cnt = max(cnt, max_cnt)
return max_cnt
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hash_l = Counter(ransomNote)
hash_c = Counter(magazine)
for l in hash_l:
if hash_l[l] > hash_c.get(l,0):
return False
return True
7.5 股票
8 Ranking
8.1 Quick Sort

class Solution:
def quick_sort(self, nums: list[int], left: int, right: int):
"""快速排序"""
if left >= right: # 子数组长度为 1 时终止递归
return
pivot = self.partition(nums, left, right) # 哨兵划分
self.quick_sort(nums, left, pivot - 1) # 递归左子数组
self.quick_sort(nums, pivot + 1, right) # 递归右子数组
def partition(self, nums: list[int], left: int, right: int) -> int:
"""哨兵划分"""
i, j = left, right
pivot = nums[left] # 以 nums[left] 为基准数
while i < j:
while i < j and nums[j] >= pivot: # 从右向左找首个小于基准数的元素
j -= 1
while i < j and nums[i] <= pivot: # 从左向右找首个大于基准数的元素
i += 1
nums[i], nums[j] = nums[j], nums[i] # 元素交换
nums[i], nums[left] = nums[left], nums[i] # 将基准数交换至两子数组的分界线
return i
if __name__ == "__main__":
# 快速排序
nums = [2, 4, 1, 0, 3, 5]
k = 3
solution = Solution()
solution.quick_sort(nums, 0, len(nums) - 1)
print("快速排序完成后 nums =", nums)
215.数组中的第k个最大元素: https://leetcode.cn/problems/kth-largest-element-in-an-array/
Solution 1: 内置排序函数
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
return sorted(nums)[len(nums)-k]
# TC: O(nlogn), n是数组元素数量
# SC: O(n)
Solution 2: 基于Quick Sort的Quick Select
class Solution:
def findKthLargest(self, nums, k):
def quick_select(nums, k):
pivot = random.choice(nums) # 随机选择基准数
# 将大于、小于、等于 pivot 的元素划分至 big, small, equal 中
big, equal, small = [], [], []
for num in nums:
if num > pivot:
big.append(num)
elif num < pivot:
small.append(num)
else:
equal.append(num)
# 找有序数组的第k大的数,从右向左数
if k <= len(big): # 第 k 大元素在 big 中,递归划分
return quick_select(big, k)
if len(nums) - len(small) < k: # 第 k 大元素在 small 中,递归划分
return quick_select(small, k - len(nums) + len(small))
return pivot # 第 k 大元素在 equal 中,直接返回 pivot
return quick_select(nums, k)
# TC: O(n)
# SC: O(n)
if __name__ == "__main__":
import random
nums = [2, 4, 1, 0, 3, 5]
k = 3
solution = Solution()
num = solution.findKthLargest(nums, k)
print(f"The kth largest number is {num}")
面试题Smallest K Numbers:https://leetcode.cn/problems/smallest-k-lcci/
class Solution:
def smallestK(self, arr: List[int], k: int) -> List[int]:
if k == 0 or not arr:
return []
def quick_sort(arr, k):
if not arr:
return []
pivot = random.choice(arr)
big, small, equal = [], [], []
for num in arr:
if num > pivot:
big.append(num)
elif num < pivot:
small.append(num)
else:
equal.append(num)
# 找有序数组的第k小的数,从左向右数
if k <= len(small):
return quick_sort(small, k)
elif k <= len(small) + len(equal):
return small + equal[:k - len(small)]
else:
return small + equal + quick_sort(big, k - len(small) - len(equal))
return quick_sort(arr, k)
8.2 Merge Sort

观察发现,归并排序与二叉树后序遍历的递归顺序是一致的。
- 后序遍历:先递归左子树,再递归右子树,最后处理根节点。
- 归并排序:先递归左子数组,再递归右子数组,最后处理合并。
def merge_sort(nums: list[int], left: int, right: int):
"""
divide stage:
[9, 7, 5, 6, 4]
[9, 7, 5],[6, 4]
[9, 7],[5],[6, 4]
[9],[7],[5],[6, 4]
[9],[7],[5],[6],[4]
merge stage:
[7, 9],[5],[6],[4]
[5, 7, 9],[6],[4]
[5, 7, 9],[4, 6]
[4, 5, 6, 7, 9]
"""
# base case: when subarray length is 1, recursion terminates
if left >= right:
return
# divide stage
mid = (left + right) // 2 # calculate midpoint
merge_sort(nums, left, mid) # recursion over left subarray
merge_sort(nums, mid + 1, right) # recursion over right subarray
# merge stage
merge(nums, left, mid, right)
def merge(nums: list[int], left: int, mid: int, right: int):
tmp = [0] * (right - left + 1) # store the merged subarray for update
i, j = left, mid + 1 # The left subarray interval is [left, mid]; The right subarray interval is [mid+1, right]
k = 0 # for tmp array
while i <= mid and j <= right: # 当左右子数组都还有元素时,进行比较并将较小的元素复制到临时数组中
if nums[i] <= nums[j]:
tmp[k] = nums[i]
i += 1
else:
tmp[k] = nums[j]
j += 1
k += 1
while i <= mid: # 将左子数组和右子数组的剩余元素复制到临时数组中
tmp[k] = nums[i]
i += 1
k += 1
while j <= right:
tmp[k] = nums[j]
j += 1
k += 1
for k in range(0, len(tmp)): # 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间
nums[left + k] = tmp[k]
if __name__=='__main__':
record = [9, 7, 5, 6, 4]
merge_sort(record, 0, len(record)-1)
print(record)
剑指Offer51.逆序对: https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solutions/622496/jian-zhi-offer-51-shu-zu-zhong-de-ni-xu-pvn2h/

class Solution:
def reversePairs(self, record: List[int]) -> int:
def merge_sort(L, R):
# base case: 当子数组长度为1时,停止divide
if L >= R: return 0
m = L + (R-L)//2
cnt = merge_sort(L, m) + merge_sort(m+1, R)
# 合并两个有序子数组,并计算跨子数组的逆序对
pos = L
i, j = L, m+1
while i <= m and j<= R:
if record[i] <= record[j]:
tmp[pos] = record[i]
i += 1
else:
tmp[pos] = record[j]
j += 1
cnt += m - i + 1 # 逆序对计算: 左子数组从i到m的元素都大于右子数组的record[j]
pos += 1
# 当左边的数组没有遍历完成后,直接将剩余元素加入到临时数组中
while i <= m:
tmp[pos] = record[i]
i += 1
pos += 1
# 当右边的数组没有遍历完成后,直接将剩余元素加入到临时数组中
while j <= R:
tmp[pos] = record[j]
j += 1
pos += 1
# 将排序后的子数组写回原数组
record[L:R+1] = tmp[L:R+1]
return cnt
n = len(record)
tmp = [0] * n
return merge_sort(0, n-1)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~