文章目录
12 图
12.1 【DFS】岛屿数量
遍历图中所有的位置,如果该位置为 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】被围绕的区域
反向思考,与边界的 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】除法求值
这道题的核心是根据 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】【拓扑排序】课程表
该题就是考察判断一个有向无环图内是否有环,但是这里要多加一步,判断目前的课程是否曾经被访问过,否则会超时。
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
在上一题的基础上保留访问到的课程即可,最后倒序输出。
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】蛇梯棋
利用广度优先遍历找到最快到达终点的那条路径,同时需要记录走的步数和已经访问过的结点,难点在于如何表示图中坐标。
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】最小基因变化
从基因的每个字母出发,找到存在于 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:
tmp = list(word)
tmp[i] = s
tmp = "".join(tmp)
if tmp in right:
return step + 1
if tmp in wordList and tmp not in visited:
visited.add(tmp)
cur.append(tmp)
step += 1
left = cur
if len(left) > len(right):
left,right = right,left
return 0
14 字典树
14.1 【Trie】实现 Trie (前缀树)
详见代码。
class Node():
def __init__(self):
self.child = collections.defaultdict(Node)
self.is_word = False
class Trie:
def __init__(self):
self.root = Node()
def insert(self, word: str) -> None:
cur = self.root
for w in word:
cur = cur.child[w]
cur.is_word = True
def search(self, word: str) -> bool:
cur = self.root
for w in word:
cur = cur.child.get(w)
if cur == None:
return False
return cur.is_word
def startsWith(self, prefix: str) -> bool:
cur = self.root
for w in prefix:
cur = cur.child.get(w)
if cur == None:
return False
return True
# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
14.2 【DFS】【Trie】添加与搜索单词 - 数据结构设计
详见代码,在前缀树的基础上引入 D F S DFS DFS来判断特殊字符,如果遇到特殊字符,需要把当前结点的所有 c h i l d child child全都遍历一遍。
class Node:
def __init__(self):
self.child = collections.defaultdict(Node)
self.is_word = False
class WordDictionary:
def __init__(self):
self.root = Node()
def addWord(self, word: str) -> None:
cur = self.root
for w in word:
cur = cur.child[w]
cur.is_word = True
def DFS(self, word, index, root):
if not root:
return False
if index == len(word):
return root.is_word
if word[index] != ".":
return root and self.DFS(word,index+1,root.child.get(word[index]))
else:
for cld in root.child.values():
if self.DFS(word,index+1,cld):
return True
return False
def search(self, word: str) -> bool:
return self.DFS(word,0,self.root)
# Your WordDictionary object will be instantiated and called as such:
# obj = WordDictionary()
# obj.addWord(word)
# param_2 = obj.search(word)
14.3 【Trie】【DFS】单词搜索 II
构造 w o r d s words words的前缀树,然后通过 D F S DFS DFS遍历 b o a r d board board中每一个元素,如果遇到存在于前缀树中的元素,则进行 D F S DFS DFS,判断是否能找到一个完整的单词。
class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
# Trie_construction
trie = {}
for word in words:
node = trie
for w in word:
node = node.setdefault(w,{})
node['end'] = 1
ans = []
row = len(board)
col = len(board[0])
def DFS(i,j,trie,cur):
if board[i][j] not in trie:
return
s = board[i][j]
trie = trie[s]
if 'end' in trie and trie['end'] == 1:
ans.append(cur+s)
trie['end'] = 0
board[i][j] = '#'
for x,y in [[-1,0],[1,0],[0,-1],[0,1]]:
tmp_i = i + x
tmp_j = j + y
if 0<=tmp_i<row and 0<=tmp_j<col and board[tmp_i][tmp_j] != '#':
DFS(tmp_i,tmp_j,trie,cur+s)
board[i][j] = s
for i in range(row):
for j in range(col):
DFS(i,j,trie,'')
return ans
15 回溯
15.1 【DFS】电话号码的字母组合
详见代码。
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
dict = {'2':'abc','3':'def','4':'ghi','5':'jkl','6':'mno','7':'pqrs','8':'tuv','9':'wxyz'}
if len(digits) == 0:
return []
self.ans = []
self.cur = ''
def DFS(index):
if len(self.cur) == len(digits):
self.ans.append(self.cur)
else:
for s in dict[digits[index]]:
self.cur += s
DFS(index+1)
self.cur = self.cur[:-1]
DFS(0)
return self.ans
15.2 【DFS】组合
题目地址:https://leetcode.cn/problems/combinations/description/?envType=study-plan-v2&envId=top-interview-150
详见代码。
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
self.ans = []
self.cur = []
def DFS(num):
if len(self.cur) == k:
self.ans.append(self.cur)
else:
for i in range(num,n+1):
self.cur.append(i)
DFS(i+1)
self.cur = self.cur[:-1]
DFS(1)
return self.ans
15.3 【DFS】全排列
题目地址:https://leetcode.cn/problems/permutations/?envType=study-plan-v2&envId=top-interview-150
固定 n u m s nums nums中的每一个数作为第一位,然后进行 D F S DFS DFS,在 D F S DFS DFS时记得去重,已遍历过的数字不能出现在排列中。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
self.n = len(nums)
self.ans = []
self.cur = []
def DFS():
if len(self.cur) == self.n:
self.ans.append(self.cur)
return
else:
for i in range(0,self.n):
if nums[i] not in self.cur:
self.cur.append(nums[i])
DFS()
self.cur = self.cur[:-1]
for i in range(self.n):
self.cur.append(nums[i])
DFS()
self.cur = []
return self.ans
15.4 【DFS】【剪枝】组合总和
在 D F S DFS DFS的基础引入剪枝,如果当前的和超过了 t a r g e t target target,则提前终止循环。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def DFS(candidates,start,end,cur):
if sum(cur) == target:
ans.append(cur)
return
else:
for i in range(start,end):
if sum(cur) + candidates[i] > target:
break
else:
DFS(candidates,i,l,cur+[candidates[i]])
l = len(candidates)
candidates.sort()
ans = []
cur = []
DFS(candidates,0,l,cur)
return ans
15.5 【DFS】【剪枝】N 皇后 II
题目地址:https://leetcode.cn/problems/n-queens-ii/?envType=study-plan-v2&envId=top-interview-150
要想皇后不冲突,就要保证每一行每一列以及左右斜着的线上只存在一个皇后,这里我们可以以此为剪枝条件:每一行可以作为索引来遍历,每一列可以单独用一个数组维护是否存在皇后,对于左右斜着的线,可以发现,左斜线上的位置横纵坐标相加是一个固定值,右斜线上的位置横纵坐标之差是一个固定值,以此分别用一个数字维护是否存在皇后。
class Solution:
def totalNQueens(self, n: int) -> int:
def DFS(idx):
if idx == n:
self.ans += 1
return
else:
for i in range(n):
if not col_flag[i] and not left_flag[idx+i] and not right_flag[idx-i+n-1]:
col_flag[i] = left_flag[idx+i] = right_flag[idx-i+n-1] = 1
DFS(idx+1)
col_flag[i] = left_flag[idx+i] = right_flag[idx-i+n-1] = 0
m = n*2-1
col_flag, left_flag, right_flag = [0]*n, [0]*m, [0]*m
self.ans = 0
DFS(0)
return self.ans
15.6 【DFS】【剪枝】括号生成
首先需要添加左括号,然后才能添加右括号,所以在 D F S DFS DFS的过程中要保持左括号的数量要小于等于右括号,根据这一规则进行剪枝。
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
self.ans = []
def DFS(cur,left,right):
if left == 0 and right == 0:
self.ans.append(cur)
return
if left > right:
return
if left > 0:
DFS(cur+'(',left-1,right)
if right > 0:
DFS(cur+')',left,right-1)
DFS('',n,n)
return self.ans
15.7 【DFS】单词搜索
题目地址:https://leetcode.cn/problems/word-search/description/?envType=study-plan-v2&envId=top-interview-150
注意 D F S DFS DFS中每次都要给出一个返回值,否则最后无法判断是否找到了单词。
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
row = len(board)
col = len(board[0])
def DFS(i,j,l):
if l == len(word)-1 and board[i][j] == word[l]:
return True
elif board[i][j] == word[l]:
s = board[i][j]
board[i][j] = '#'
for x,y in [[0,1],[0,-1],[1,0],[-1,0]]:
tmp_i = x + i
tmp_j = y + j
if 0<=tmp_i<row and 0<=tmp_j<col and board[tmp_i][tmp_j]!= '#':
if DFS(tmp_i,tmp_j,l+1):
return True
board[i][j] = s
for i in range(row):
for j in range(col):
if DFS(i,j,0):
return True
return False