文章目录
- 12 图
- 12.1 【DFS】岛屿数量
- 12.2 【DFS】被围绕的区域
- 12.3 【DFS】克隆图
- 12.4 【DFS】除法求值
- 12.5 【DFS】【拓扑排序】课程表
- 12.6 【DFS】【拓扑排序】课程表 II
- 13 图的广度优先搜索
- 13.1 【BFS】蛇梯棋
- 13.2 【BFS】最小基因变化
- 13.3 【双向BFS】单词接龙
- 14 字典树
- 14.1 【Trie】实现 Trie (前缀树)
- 14.2 【DFS】【Trie】添加与搜索单词 - 数据结构设计
- 14.3 【Trie】【DFS】单词搜索 II
- 15 回溯
- 15.1 【DFS】电话号码的字母组合
- 15.2 【DFS】组合
- 15.3 【DFS】全排列
- 15.4 【DFS】【剪枝】组合总和
- 15.5 【DFS】【剪枝】N 皇后 II
- 15.6 【DFS】【剪枝】括号生成
- 15.7 【DFS】单词搜索
12 图
12.1 【DFS】岛屿数量
题目地址:https://leetcode.cn/problems/number-of-islands/description/?envType=study-plan-v2&envId=top-interview-150
遍历图中所有的位置,如果该位置为 1 1 1,则向外扩展将与之相连的 1 1 1全部变为 0 0 0,记录需要进行扩展的 1 1 1的个数。
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
row = len(grid)
col = len(grid[0])
ans = 0
def DFS(i,j):
grid[i][j] = "0"
for x,y in [[0,1],[1,0],[0,-1],[-1,0]]:
tmp_i = i + x
tmp_j = j + y
if 0 <= tmp_i < row and 0 <= tmp_j < col and grid[tmp_i][tmp_j] == "1":
DFS(tmp_i,tmp_j)
for i in range(row):
for j in range(col):
if grid[i][j] == "1":
DFS(i,j)
ans += 1
return ans
12.2 【DFS】被围绕的区域
题目地址:https://leetcode.cn/problems/surrounded-regions/description/?envType=study-plan-v2&envId=top-interview-150
反向思考,与边界的 O O O相连的位置如果也是 O O O,则这些位置一定不会被覆盖,所以从区域边界向内 D F S DFS DFS,找出所有不会被覆盖的位置,再将区域内剩下的 O O O覆盖为 X X X即可。
class Solution:
def solve(self, board: List[List[str]]) -> None:
row = len(board)
col = len(board[0])
def DFS(i,j):
if 0 <= i < row and 0 <= j < col and board[i][j] == "O":
board[i][j] = "#"
for x,y in [[0,1],[0,-1],[-1,0],[1,0]]:
tmp_i = i + x
tmp_j = j + y
DFS(tmp_i,tmp_j)
for i in range(row):
DFS(i,0)
DFS(i,col-1)
for j in range(col):
DFS(0,j)
DFS(row-1,j)
for i in range(row):
for j in range(col):
if board[i][j] == "O":
board[i][j] = "X"
elif board[i][j] == "#":
board[i][j] = "O"
12.3 【DFS】克隆图
题目地址:https://leetcode.cn/problems/clone-graph/description/?envType=study-plan-v2&envId=top-interview-150
该题与克隆链表比较像,重点在于如何把每个结点的 n e i g h b o r s neighbors neighbors全部克隆下来,这里需要额外引入一个字典来防止图中结点被重复访问,然后按照图中结点进行深度优先遍历即可。
"""
# Definition for a Node.
class Node:
def __init__(self, val = 0, neighbors = None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
"""
from typing import Optional
class Solution:
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
self.node_list = {}
def DFS(node):
if not node:
return
if node in self.node_list:
return self.node_list[node]
new_node = Node(node.val, [])
self.node_list[node] = new_node
for n in node.neighbors:
new_node.neighbors.append(DFS(n))
return new_node
return DFS(node)
12.4 【DFS】除法求值
题目地址:https://leetcode.cn/problems/evaluate-division/description/?envType=study-plan-v2&envId=top-interview-150
这道题的核心是根据 e q u a t i o n s equations equations和 v a l u e s values values构造一个有向图,然后查找 q u e r i e s queries queries中的每对字母之间是否存在一条路径,如果存在就返回这条路径上的所有权重的乘积,否则就返回 − 1 -1 −1。
class Solution:
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
graph = {}
# graph_construction
for (x,y),v in zip(equations,values):
if x in graph:
graph[x][y] = v
else:
graph[x] = {y:v}
if y in graph:
graph[y][x] = 1/v
else:
graph[y] = {x:1/v}
# DFS
def DFS(start,end):
if start not in graph:
return -1
if start == end:
return 1
for node in graph[start].keys():
if node == end:
return graph[start][node]
elif node not in visited:
visited.add(node)
v = DFS(node,end)
if v != -1:
return graph[start][node] * v
return -1
# solve_answer
ans = []
for start,end in queries:
visited = set()
ans.append(DFS(start,end))
return ans
12.5 【DFS】【拓扑排序】课程表
题目地址:https://leetcode.cn/problems/course-schedule/description/?envType=study-plan-v2&envId=top-interview-150
该题就是考察判断一个有向无环图内是否有环,但是这里要多加一步,判断目前的课程是否曾经被访问过,否则会超时。
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
graph = {}
history_visited = set()
# graph_construction
for x,y in prerequisites:
if y in graph:
graph[y].add(x)
else:
graph[y] = {x}
def DFS(node):
if node in visited:
return False
if node in history_visited:
return True
visited.add(node)
history_visited.add(node)
if node in graph:
for n in graph[node]:
if not DFS(n):
return False
visited.remove(node)
return True
for i in range(numCourses):
if i in history_visited:
continue
visited = set()
if not DFS(i):
return False
return True
12.6 【DFS】【拓扑排序】课程表 II
题目地址:https://leetcode.cn/problems/course-schedule-ii/description/?envType=study-plan-v2&envId=top-interview-150
在上一题的基础上保留访问到的课程即可,最后倒序输出。
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
graph = {}
history_visited = set()
ans = []
# graph_construction
for x,y in prerequisites:
if y in graph:
graph[y].add(x)
else:
graph[y] = {x}
def DFS(node):
if node in visited:
return False
if node in history_visited:
return True
visited.add(node)
history_visited.add(node)
if node in graph:
for n in graph[node]:
if not DFS(n):
return False
visited.remove(node)
ans.append(node)
return True
for i in range(numCourses):
if i in history_visited:
continue
visited = set()
if not DFS(i):
return []
return ans[::-1]
13 图的广度优先搜索
13.1 【BFS】蛇梯棋
题目地址:https://leetcode.cn/problems/snakes-and-ladders/description/?envType=study-plan-v2&envId=top-interview-150
利用广度优先遍历找到最快到达终点的那条路径,同时需要记录走的步数和已经访问过的结点,难点在于如何表示图中坐标。
class Solution:
def snakesAndLadders(self, board: List[List[int]]) -> int:
n = len(board)
visited = set()
stack = [1]
ans = 1
# index
def position(num):
i = (num-1)//n
x = n-1-i
if i%2:
y = n-1-(num-1)%n
else:
y = (num-1)%n
return x,y
while stack:
cur_idx = []
for idx in stack:
for nxt in range(idx+1,min(idx+6,n*n)+1):
if nxt not in visited:
visited.add(nxt)
x,y = position(nxt)
if board[x][y] != -1:
nxt = board[x][y]
cur_idx.append(nxt)
if nxt == n*n:
return ans
ans += 1
stack = cur_idx
return -1
13.2 【BFS】最小基因变化
题目地址:https://leetcode.cn/problems/minimum-genetic-mutation/description/?envType=study-plan-v2&envId=top-interview-150
从基因的每个字母出发,找到存在于 b a n k bank bank中的下一步修改,然后进行广度优先遍历,最后判断是否有一次修改可以变为 e n d G e n e endGene endGene。
class Solution:
def minMutation(self, startGene: str, endGene: str, bank: List[str]) -> int:
if endGene not in bank:
return -1
stack = [startGene]
visited = set()
step = 1
while stack:
cur = []
for gene in stack:
for i in range(8):
for s in ['A','C','G','T']:
if gene[i] != s:
tmp = list(gene)
tmp[i] = s
tmp = ''.join(tmp)
if tmp in bank and tmp not in visited:
visited.add(tmp)
cur.append(tmp)
if tmp == endGene:
return step
step += 1
stack = cur
return -1
13.3 【双向BFS】单词接龙
题目地址:https://leetcode.cn/problems/word-ladder/description/?envType=study-plan-v2&envId=top-interview-150
这道题采用 B F S BFS BFS会超时,所以要从 b e g i n W o r d beginWord beginWord和 e n d W o r d endWord endWord分别开始进行 B F S BFS BFS,最后实现一个双向 B F S BFS BFS来优化时间复杂度。
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if endWord not in wordList:
return 0
# word_single_set
word_list = set()
for word in wordList:
for i in range(len(word)):
word_list.add(word[i])
word_list = list(word_list)
left,right = [beginWord],[endWord]
visited = set()
step = 1
while left:
cur = []
for word in left:
for i in range(len(word)):
for s in word_list:
if word[i] != s: