【leetcode刷题之路】面试经典150题(6)——图+图的广度优先搜索+字典树+回溯

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:
                            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 (前缀树)

题目地址:https://leetcode.cn/problems/implement-trie-prefix-tree/description/?envType=study-plan-v2&envId=top-interview-150

  详见代码。

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】添加与搜索单词 - 数据结构设计

题目地址:https://leetcode.cn/problems/design-add-and-search-words-data-structure/?envType=study-plan-v2&envId=top-interview-150

  详见代码,在前缀树的基础上引入 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

题目地址:https://leetcode.cn/problems/word-search-ii/description/?envType=study-plan-v2&envId=top-interview-150

  构造 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】电话号码的字母组合

题目地址:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/?envType=study-plan-v2&envId=top-interview-150

  详见代码。

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】【剪枝】组合总和

题目地址:https://leetcode.cn/problems/combination-sum/description/?envType=study-plan-v2&envId=top-interview-150

  在 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】【剪枝】括号生成

题目地址:https://leetcode.cn/problems/generate-parentheses/description/?envType=study-plan-v2&envId=top-interview-150

  首先需要添加左括号,然后才能添加右括号,所以在 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
  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小天才才

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值