目录
一、数组和字符串
1.1 LC 三数之和
1.1.1 题求
1.1.2 求解
法一:排序+双指针
# 36.18% - 788ms
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 从小到大排序
nums.sort()
n = len(nums)
res = []
# 枚举 a
for a in range(n):
# a 需和上次枚举数不同
if a > 0 and nums[a] == nums[a-1]:
continue
# c 初始指向数组最右端 ☆
c = n - 1
for b in range(a+1, n-1):
# b 需和上次枚举的数不同
if b > a+1 and nums[b] == nums[b-1]:
continue
# 需保证 b 在 c 左侧; 若 a+b+c 过大,减小 c (a+b+c>0)
while b < c and nums[b] + nums[c] > -nums[a]:
c -= 1
# 若 c 减少至与 b 重合,将不再有满足 a+b+c=0 且 b<c 的 c 了,退出循环 ☆
if b == c:
break
# 前面条件都符合,才记录当前组合 (a+b+c=0)
if nums[b] + nums[c] == -nums[a]:
res.append([nums[a], nums[b], nums[c]])
return res
参考资料:
1.2 LC 矩阵置零
1.2.1 题求
1.2.2 求解
法一:哈希集合+迭代
# 95.30% - 32ms
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m, n = len(matrix), len(matrix[0])
zero_cols = set() # 全 0 列
none_zero_rows = set() # 非全 0 行
for i in range(m):
zero_row = False # 当前行是否清零
for j in range(n):
if matrix[i][j] == 0:
zero_row = True
zero_cols.add(j)
if zero_row:
for j in range(n):
matrix[i][j] = 0
else:
none_zero_rows.add(i)
# 对非全 0 行中尚未清 0 的列清 0
for i in none_zero_rows:
for j in zero_cols:
matrix[i][j] = 0
参考资料:
1.3 LC 字母异位词分组
1.3.1 题求
1.3.2 求解
法一:哈希表+排序
# 93.31% - 40ms
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
from collections import defaultdict
hashtable = defaultdict(list)
for string in strs:
key = tuple(sorted(list(string)))
hashtable[key].append(string)
return [val for val in hashtable.values()]
参考资料:
1.4 LC 无重复字符的最长子串
1.4.1 题求
1.4.2 求解
法一:双指针
# 93.31% - 48ms
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
max_len = 0 # 不重复数组的最大长度
hashtable = {} # 不重复数组各字符及其索引
left = 0 # 不重复数组的左、右边界
# 遍历右边界
for right in range(len(s)):
# 若当前字符未曾出现,则加入不重复数组 s[left, right],更新最大长度
if s[right] not in hashtable:
hashtable[s[right]] = right # key: val = s: idx
max_len = max(max_len, right-left+1)
# 若当前字符已出现,则从上次出现的位置截断
else:
# 删除截断点之前的记录
new_left = hashtable[s[right]]+1
for i in range(left, new_left):
del hashtable[s[i]]
# 更新新的左、右边界索引
left = new_left
hashtable[s[right]] = right # key: val = s: idx
return max_len
参考资料:
1.5 LC 最长回文子串
1.5.1 题求
1.5.2 求解
法一:动态规划
# 44.82% - 4480ms
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
max_left, max_right, max_len = 0, 0, 1
# dp[i][j] 表示 i, j 处是否为回文字符串
dp = [[False for _ in range(n)] for _ in range(n)]
for i in range(n):
dp[i][i] = True
# 状态转移
for i in range(n-1, -1, -1):
for j in range(i+1, n):
if s[i] == s[j] and (j - i == 1 or dp[i+1][j-1] == True):
dp[i][j] = True
if (cur_len := j-i+1) > max_len:
max_left, max_right, max_len = i, j, cur_len
return s[max_left: max_right+1]
参考资料:
1.6 LC 递增的三元子序列 ☆
1.6.1 题求
1.6.2 求解
法一:一次遍历
# 98.72% - 42ms
class Solution:
def increasingTriplet(self, nums: List[int]) -> bool:
n = len(nums)
if n < 3:
return False
small, mid = float("inf"), float("inf")
for num in nums:
if num <= small: # num <= small < mid 令 num 插左
small = num
elif num <= mid: # small < num <= mid 令 num 插中
mid = num
elif num > mid: # small < mid < num 令 num 插右
return True
return False
参考资料:
二、链表
2.1 LC 两数相加
2.1.1 题求
2.1.2 求解
法一:基本法
# 80.38% - 52ms
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
# 链表 1 的数字 1
num1 = base1 = 0
while l1:
num1 += (l1.val * 10**base1)
l1 = l1.next
base1 += 1
# 链表 2 的数字 2
num2 = base2 = 0
while l2:
num2 += (l2.val * 10**base2)
l2 = l2.next
base2 += 1
# 数字 1 和数字 2 之和 summ
summ = num1 + num2
# 哨兵节点 / 伪头部
dummy = node = ListNode()
# 和 summ 为 0 则直接返回
if summ == 0:
return node
# 逐个分配节点
while summ != 0:
summ, residual = divmod(summ, 10)
node.next = ListNode(residual)
node = node.next
return dummy.next
法二:一次遍历
# 80.38% - 52ms
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
head = tail = None
carry = 0
while l1 or l2:
# 当前节点值与上次进位之和
n1 = l1.val if l1 else 0
n2 = l2.val if l2 else 0
summ = n1 + n2 + carry
# 尾部节点
if not head:
head = tail = ListNode(summ % 10)
else:
tail.next = ListNode(summ % 10)
tail = tail.next
# 进位
carry = summ // 10
# 指针后移
if l1:
l1 = l1.next
if l2:
l2 = l2.next
# 进位补充尾部节点
if carry > 0:
tail.next = ListNode(carry)
return head
参考资料:
2.2 LC 奇偶链表
2.2.1 题求
2.2.2 求解
法一:双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
# 节点数小于 2 的链表直接返回
if not head or not head.next:
return head
# 偶数索引链表 0, 2, 4 ...
even_head = even = head
# 奇数索引链表 1, 3, 5 ...
odd_head = odd = head.next
# 交叉链接
while odd.next and even.next:
even.next = odd.next
even = even.next
if even.next:
odd.next = even.next
odd = odd.next
# 链表尾部解耦
if not odd.next:
even.next = None # odd.next
elif not even.next:
odd.next = None # even.next
# 偶数索引链表尾部链接奇数索引链表头部
even.next = odd_head
return even_head
法一改:最简化
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
if not head:
return head
odd_head = head.next
even, odd = head, odd_head
while odd and odd.next:
even.next = odd.next
even = even.next
odd.next = even.next
odd = odd.next
even.next = odd_head
return head
参考资料:
2.3 相交链表
2.3.1 题求
2.3.2 求解
法一:交替迭代
# 86.53% - 128ms
# 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) -> ListNode:
# 若不相交, 则有 A + B = B + A, 最终同时为 None
# 若相交, 则有 A + C + B = B + C + A, 最终在 C 头相遇
nodeA, nodeB = headA, headB
while nodeA or nodeB:
# 若有两个节点相同, 则相交
if nodeA == nodeB:
return nodeA
# 第一次由 A 交换到 B
if not nodeA:
nodeA = headB
else:
nodeA = nodeA.next
# 第一次由 B 交换到 A
if not nodeB:
nodeB = headA
else:
nodeB = nodeB.next
# 不想交, 最终同时为 None
return None
参考资料:
三、树和图
3.1 LC 二叉树的中序遍历
3.1.1 题求
3.1.2 求解
法一:递归
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
def inorder(node):
if node:
inorder(node.left)
res.append(node.val)
inorder(node.right)
return
inorder(root)
return res
法二:迭代
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
stack = []
while stack or root:
if root:
stack.append(root)
root = root.left
else:
root = stack.pop()
res.append(root.val) # 中序记录时机
root = root.right
return res
参考资料:
3.2 LC 二叉树的锯齿形层次遍历
3.2.1 题求
3.2.2 求解
法一:正常出入队+正反记录 - 比正常记录然后每两层结果 reverse 更高效
# 97.31% - 24ms
from collections import deque
class Solution:
def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res = []
queue = deque([root])
reverse = 0 # 逆序标志
while queue:
num = len(queue)
tmp = [-1 for _ in range(num)]
for idx in range(num):
# 正常弹出和压入
cur = queue.popleft()
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
# 正反记录
if reverse == 1:
idx = -idx - 1
tmp[idx] = cur.val
# 记录当前层遍历结果
res.append(tmp)
# 翻转标志
reverse = 1 - reverse
return res
法二:双反双端队列
# 97.31% - 24ms
from collections import deque
class Solution:
def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
res = []
queue1 = deque([root])
queue2 = deque()
while queue1 or queue2:
tmp = []
if queue1:
for _ in range(len(queue1)):
# 正弹出
cur = queue1.popleft()
tmp.append(cur.val)
# 正压入
if cur.left:
queue2.append(cur.left)
if cur.right:
queue2.append(cur.right)
# 除了记录照常, 全部镜像相反!!!
else:
for _ in range(len(queue2)):
# 反弹出
cur = queue2.pop() # 反
tmp.append(cur.val)
# 反压入
if cur.right: # 反
queue1.appendleft(cur.right) # 反
if cur.left: # 反
queue1.appendleft(cur.left) # 反
# 记录当前层遍历结果
res.append(tmp)
return res
参考资料:
3.3 LC 从前序与中序遍历序列构造二叉树 ☆
3.3.1 题求
3.3.2 求解
法一:递归
# 90.32% - 40ms
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
# preorder - 根节点 - 左子树 - 右子树 - [3,9,20,15,7]
# inorder - 左子树 - 根节点 - 右子树 - [9,3,15,20,7]
def helper(lhs, rhs):
if lhs > rhs:
return None
# 当前根节点 node
node = TreeNode(pre.popleft())
# 当前根节点 node 在中序遍历中的坐标 - 用于划分左、右子树
idx = hashmap[node.val]
# 左子树范围
node.left = helper(lhs, idx-1)
# 右子树范围
node.right = helper(idx+1, rhs)
# 返回当前根节点 node
return node
# 使用双端队列代替 list.pop(0) 降低运算量
pre = collections.deque(preorder)
# 中序遍历值:索引
hashmap = {val: idx for idx, val in enumerate(inorder)}
return helper(0, len(inorder)-1)
# 98.47% - 32ms
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def helper(lhs, rhs):
if lhs > rhs:
return None
# 当前根节点 node
node = TreeNode(pre.pop())
# 当前根节点 node 在中序遍历中的坐标 - 用于划分左、右子树
idx = hashmap[node.val]
# 左子树范围
node.left = helper(lhs, idx-1)
# 右子树范围
node.right = helper(idx+1, rhs)
# 返回当前根节点 node
return node
# 使用 stack 代替 list.pop(0) 降低运算量
n = len(preorder)
pre = [preorder[i] for i in range(n-1, -1, -1)]
# 中序遍历值:索引
hashmap = {val: idx for idx, val in enumerate(inorder)}
return helper(0, n-1)
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def helper(lhs, rhs):
nonlocal node_idx
if lhs > rhs:
return None
# 当前根节点 node
node = TreeNode(preorder[node_idx])
node_idx += 1
# 当前根节点 node 在中序遍历中的坐标 - 用于划分左、右子树
idx = hashmap[node.val]
# 左子树范围
node.left = helper(lhs, idx-1)
# 右子树范围
node.right = helper(idx+1, rhs)
# 返回当前根节点 node
return node
# 使用非局部变量 node_idx 代替 list.pop(0) 降低运算量
node_idx = 0
# 中序遍历值:索引
hashmap = {val: idx for idx, val in enumerate(inorder)}
return helper(0, len(inorder)-1)
法二:迭代
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder:
return None
root = TreeNode(preorder[0])
stack = [root] # 前序遍历首个节点 - 根节点
inorder_idx = 0 # 中序遍历首个节点索引
# 依次枚举前序遍历中除了首个节点外的每个节点
for i in range(1, len(preorder)):
# 前序遍历第 i 个节点
preorder_val = preorder[i]
# 栈顶当前节点
node = stack[-1]
# 如果 inorder[inorder_idx] 和栈顶节点不同,于是将当前节点 node 作为栈顶节点的左子节点
if node.val != inorder[inorder_idx]:
node.left = TreeNode(preorder_val)
stack.append(node.left) # 左子节点入栈
# 如果 inorder_idx 恰好指向栈顶节点,那么不断地弹出栈顶节点 inorder_idx,
# 并将当前节点作为最后一个弹出的节点的右子节点
else:
while stack and stack[-1].val == inorder[inorder_idx]:
node = stack.pop() # 栈顶节点
inorder_idx += 1 # 栈顶节点
node.right = TreeNode(preorder_val)
stack.append(node.right)
return root
参考资料:
3.4 LC 从前序与中序遍历序列构造二叉树 ☆
3.4.1 题求
3.4.2 求解
法一:迭代 - BFS
# 80.27% - 56ms
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root:
return root
queue = collections.deque()
queue.append(root)
while queue:
nxt = None
num = len(queue)
for _ in range(num):
# 当前节点
cur = queue.popleft()
# 子节点入队
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
# 右节点连接
if not nxt:
nxt = cur
else:
nxt.next = cur
nxt = nxt.next
return root
法二:递归
# 80.27% - 56ms
class Solution:
def connect(self, root: 'Node') -> 'Node':
def dfs(cur, nxt):
if not cur:
return None
# 当前连接
cur.next = nxt
# 左子节点连接右子节点
dfs(cur.left, cur.right)
# 右子节点连接父节点的后继节点的左子节点
dfs(cur.right, None if not cur.next else cur.next.left)
dfs(root, None)
return root
参考资料:
3.5 LC 二叉搜索树中第K小的元素
3.5.1 题求
3.5.2 求解
法一:中序遍历+迭代
# 86.77% - 44ms
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
stack = [root]
while stack or root:
if root:
stack.append(root)
root = root.left
else:
root = stack.pop()
k -= 1
if k == 0:
return root.val
root = root.right
return
参考资料:
3.6 LC 岛屿数量
3.6.1 题求
3.6.2 求解
法一:DFS
# 59.19% - 112ms
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(i, j):
# 坐标越界或不为新岛屿, 返回 False
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] == "0":
return False
# 已经 DFS 的节点置 0
grid[i][j] = "0"
# DFS 所有相邻节点
dfs(i-1, j)
dfs(i+1, j)
dfs(i, j-1)
dfs(i, j+1)
# 当前所有相邻节点为同一岛屿, 返回 True
return True
num = 0
m, n = len(grid), len(grid[0])
for i in range(m):
for j in range(n):
if dfs(i, j):
num += 1
return num
参考资料: