这一篇主要记录一些固定招式。难度不在于算法的思考,而在于写对。
模板
堆:Leetcode 23 Merge k Sorted Lists 合并K个升序链表
题目
意义
写法、模板、优先队列
随想
这个题就是个优先队列的模板题,注意优先队列的实现方式
复杂度
- 时间
O ( N k log k ) \mathcal O(Nk\log k) O(Nklogk) - 空间
O ( k ) \mathcal O(k) O(k)
代码
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class PQ:
def __init__(self):
self.array = [0]
def comp(self, n1, n2):
return n1.val < n2.val
# 递归下滤
def down(self, idx):
left_idx = 2 * idx
right_idx = 2 * idx + 1
root = idx
size = len(self.array) - 1
if left_idx <= size and self.comp(self.array[left_idx], self.array[root]):
root = left_idx
if right_idx <= size and self.comp(self.array[right_idx], self.array[root]):
root = right_idx
if root != idx:
self.array[idx], self.array[root] = self.array[root], self.array[idx]
self.down(root)
def build_map(self, A):
self.array += A
for idx in range((len(self.array) - 1) // 2, 0, -1):
self.down(idx)
def pop(self):
res = self.array[1]
last_node = self.array.pop(-1)
if not self.empty():
self.array[1] = last_node
self.down(1)
return res
def insert(self, node):
self.array.append(node)
# 递归上浮
idx = len(self.array) - 1
root_idx = idx // 2
while root_idx > 0 and self.comp(self.array[idx], self.array[root_idx]):
self.array[idx], self.array[root_idx] = self.array[root_idx], self.array[idx]
idx = root_idx
root_idx = idx // 2
def empty(self):
return len(self.array) == 1
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
pq = PQ()
pq.build_map([l for l in lists if l])
psedo = ListNode(0)
cur = psedo
while not pq.empty():
node = pq.pop()
node_next = node.next
if node_next:
pq.insert(node_next)
cur.next = node
cur = cur.next
return psedo.next
并查集:Leetcode 200 Number of Islands 岛屿数量
题目
意义
并查集、写法、模板
随想
这个题难度不大,这里没有用BFS、DFS的写法,采用了并查集。并查集有两个操作:find_root
和joint_two_node
,可以把这个题当作并查集模板
尤其注意joint_two_node
要判断两个节点是否一样,否则可能陷入死循环
复杂度
- 时间
O ( m n ) \mathcal O(m n) O(mn) - 空间
O ( m n ) \mathcal O(mn) O(mn)
代码
class Solution:
def get_root(self, father_table, idx):
old_idx = idx
while father_table[idx] != -1:
idx = father_table[idx]
# 并查集的加速
while old_idx != idx:
temp = father_table[old_idx]
father_table[old_idx] = idx
old_idx = temp
return idx
# 合并两个节点
def joint_two_node(self, father_table, n1, n2):
r1 = self.get_root(father_table, n1)
r2 = self.get_root(father_table, n2)
# 这里一定要加if,否则会陷入死循环
if (r1 != r2):
father_table[r2] = r1
def numIslands(self, grid: List[List[str]]) -> int:
m = len(grid)
if m == 0:
return 0
n = len(grid[0])
father_table = [-1] * (m * n)
for r in range(m):
for c in range(n):
if grid[r][c] == "0":
father_table[r * n + c] = -2
continue
if r > 0 and grid[r - 1][c] == "1":
self.joint_two_node(father_table, r * n + c - n, r * n + c)
if c > 0 and grid[r][c - 1] == "1":
self.joint_two_node(father_table, r * n + c - 1, r * n + c)
result = 0
for r in range(m):
for c in range(n):
if father_table[r * n + c] == -1:
result += 1
return result
链表、哈希:Leetcode 146 LRU Cache LRU缓存机制
题目
意义
链表、哈希、写法、模板
随想
这个题吧,也不难。双向链表+Dict。Dict以key为key,以node为value.
就是写法上要注意
- Python的链表写法. 尤其注意python中自己写函数的时候,要加self参数,很容易忘,然后疯狂报错
- dict索引之后的那一句可能比较长,建议单独拿成变量,否则容易出bug
node = self.node_d[key]
复杂度
- 时间
O ( 1 ) \mathcal O(1) O(1) - 空间
O ( n ) \mathcal O(n) O(n)
代码
class DNode:
def __init__(self, key, value):
self.key = key
self.value = value
self.pre = None
self.nex = None
class LRUCache:
def __init__(self, capacity: int):
# psedo node
self.head = DNode(None, None)
self.tail = DNode(None, None)
self.head.nex = self.tail
self.tail.pre = self.head
# dict
self.node_d = {}
self.remain_size = capacity
def get(self, key: int) -> int:
if key in self.node_d:
node = self.node_d[key]
self.moveToHead(node)
return node.value
else:
return -1
def put(self, key: int, value: int) -> None:
if key in self.node_d:
node = self.node_d[key]
node.value = value
self.moveToHead(node)
else:
node = DNode(key, value)
self.node_d[key] = node
self.addHead(node)
self.remain_size -= 1
if self.remain_size == -1:
self.node_d.pop(self.tail.pre.key)
self.removeNode(self.tail.pre)
self.remain_size += 1
def addHead(self, node):
node.nex = self.head.nex
node.pre = self.head
node.nex.pre = node
self.head.nex = node
def removeNode(self, node):
node.nex.pre = node.pre
node.pre.nex = node.nex
def moveToHead(self, node):
self.removeNode(node)
self.addHead(node)
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
二叉树的前、中、后序、层次遍历,非递归、迭代器写法
意义
写法 模板
随想
- 二叉树的非递归版本常考,这里再进一步给出迭代器的写法,从而能在迭代过程中,把除了结果以外的空间消耗降到树的高度 O ( h ) \mathcal O(h) O(h)
- 这里后序,需要涉及到状态,0表示向右,1表示向上。状态可以看作是递归版本的指令寄存器
代码
class Solution:
### 前序
def preorderTraversal(self, root: TreeNode) -> List[int]:
def get_next(sta):
if not sta:
return None
cur = sta.pop()
if cur.right:
sta.append(cur.right)
if cur.left:
sta.append(cur.left)
return cur
sta = []
res = []
if root:
sta.append(root)
while True:
cur = get_next(sta)
if not cur:
break
res.append(cur.val)
return res
### 中序
def inorderTraversal(self, root: TreeNode) -> List[int]:
def add_to_stack(sta, cur):
while cur:
sta.append(cur)
cur = cur.left
def get_next(sta):
if not sta:
return None
cur = sta.pop()
add_to_stack(sta, cur.right)
return cur
res = []
sta = []
add_to_stack(sta, root)
while True:
cur = get_next(sta)
if cur is None:
break
res.append(cur.val)
return res
### 后序
def postorderTraversal(self, root: TreeNode) -> List[int]:
def add_left(sta, cur):
while cur: # 0 means right, 1 means up
sta.append((cur, 0))
cur = cur.left
def get_next(sta):
if not sta:
return None
cur, stat = sta.pop()
if stat == 0:
while True:
if not cur.right:
break
sta.append((cur, 1))
add_left(sta, cur.right)
cur, stat = sta.pop()
return cur
sta = []
res = []
add_left(sta, root)
while True:
cur = get_next(sta)
if not cur:
break
res.append(cur.val)
return res
### 层次
def levelOrder(self, root: TreeNode) -> List[List[int]]:
from queue import Queue
def get_next(que):
if que.empty():
return None, None
cur, height = que.get()
if cur.left:
que.put((cur.left, height + 1))
if cur.right:
que.put((cur.right, height + 1))
return cur, height
res = []
que = Queue()
cur_height = 1
cur_res = []
if root:
que.put((root, 1))
while True:
cur, height = get_next(que)
if not cur:
break
if height != cur_height:
cur_height = height
res.append(cur_res)
cur_res = []
cur_res.append(cur.val)
if cur_res:
res.append(cur_res)
return res
写法
使用成员变量:Leetcode 236 Lowest Common Ancestor of a Binary Tree 二叉树的最近公共祖先
题目
意义
写法、DFS
随想
这个题能写对,但是想写的简洁还是不容易
最简洁的写法是一次DFS
判断左右两个分支是不是都有p或q,
或者是左右两个分支一个有,但是当前节点是p或q
复杂度
- 时间
O ( N ) \mathcal O(N) O(N) - 空间
O ( N ) \mathcal O(N) O(N)
代码
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def dfs(self, node, p, q):
if not node:
return False
left_res = self.dfs(node.left, p, q)
right_res = self.dfs(node.right, p, q)
if ((node is p or node is q) and (left_res or right_res)) or (left_res and right_res):
self.res = node
return left_res or right_res or node is p or node is q
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
self.dfs(root, p, q)
return self.res
使用成员变量:Leetcode 124 Binary Tree Maximum Path Sum 二叉树中的最大路径和
题目
意义
写法、递归
随想
这个题主要在于写法。其实是一个DFS,造一个类内属性self.maxSum = float("-inf")
就方便了许多。如果不用类内属性,而当作返回值的话,很容易写的冗长
复杂度
- 时间
O ( N ) \mathcal O(N) O(N) - 空间
O ( N ) \mathcal O(N) O(N)
代码
# 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 __init__(self):
self.maxSum = float("-inf")
def maxSinglePath(self, node):
if not node:
return 0
leftSum = max(self.maxSinglePath(node.left), 0)
rightSum = max(self.maxSinglePath(node.right), 0)
self.maxSum = max(self.maxSum, leftSum + rightSum + node.val)
return node.val + max(leftSum, rightSum)
def maxPathSum(self, root: TreeNode) -> int:
self.maxSinglePath(root)
return self.maxSum
用辅助函数简化写法:Leetcode 25 Reverse Nodes in k-Group K 个一组翻转链表
题目
意义
写法、链表、模拟
随想
这个题属于模拟。**难在写对。**中间有很多细节的地方。可以把翻转子链表单独拿出来写成一个函数,会清晰很多
复杂度
- 时间
O ( n ) \mathcal O(n) O(n) - 空间
O ( 1 ) \mathcal O(1) O(1)
代码
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# return new head and new tail
def reversePart(self, head, tail):
cur, prev = head, tail.next
while prev != tail:
n = cur.next
cur.next = prev
prev = cur
cur = n
return tail, head
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
psedo = ListNode(0)
psedo.next = head
head = psedo
while True:
right = head
for _ in range(k):
if not right:
break
right = right.next
if not right:
return psedo.next
self.reversePart(head.next, right)
temp = head.next
head.next = right
head = temp
用四个局部变量写计算器:Leetcode 772 Basic Calculator III 基本计算器III
题目
意义
写法、指针、自动机
随想
- 这个题属于模拟。**难在写对。**中间有很多细节的地方。用四个局部变量可以简化整个过程,一个表示当前的数字;一个表示当前的乘除项;一个表示结果;一个表示上次的运算符,以对当前数值运算
- 遇到括号,则化为子问题
- 参考代码启发自博客:https://blog.csdn.net/m0_46202073/article/details/114729707,这种写法代码量很少
复杂度
- 时间
O ( n ) \mathcal O(n) O(n) - 空间
O ( n ) \mathcal O(n) O(n),最坏情况很多括号
代码
class Solution:
def calculate_item(self):
res = 0
tmp = 0
num = 0
opt = '+'
while self.ptr < len(self.s):
c = self.s[self.ptr]
self.ptr += 1
if c == '(':
num = self.calculate_item()
elif c not in {')', '+', '-', '*', '/'}:
num = num * 10 + ord(c) - ord('0')
if c in {')', '+', '-', '*', '/'} or self.ptr == len(self.s):
if opt == '+':
tmp += num
elif opt == '-':
tmp -= num
elif opt == '*':
tmp *= num
else: # correct division
if tmp * num < 0:
tmp = -(-tmp // num)
else:
tmp //= num
opt = c
num = 0
if c in {')', '+', '-'} or self.ptr == len(self.s):
res += tmp
tmp = 0
if c == ')' or self.ptr == len(self.s):
return res
def calculate(self, s: str) -> int:
self.ptr = 0
self.s = s
return self.calculate_item()