DFS解题思路

1.理论知识:

深度优先搜索算法(英语:Depth-First-Search,简称DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。

2.条件

在设计DFS函数的时候,首先写的语句就是弹出终止语句。之后才是递归的语句,否则会一致陷入循环不能弹出。
主要有三个函数:

  • 1.主函数:要做的有两件事:第一,处理边界情况,例如图为空的情况,初始节点满足条件的情况,第二,遍历整个图,
  • 2.DFS 递归函数:这个函数是一个递归函数,里面调用了辅助函数,DFS 函数只需要对当前节点的子节点(如果是无向图则临近节点)进行遍历即可,其他功能通过辅助函数实现。
  • 3.辅助函数:里面包含了 is_valid 以及 match 两个子函数。分别用作判断子节点是否合法,以及当前状态是否符合条件。

3.相关题目

98. 验证二叉搜索树

题目:定一个二叉树,判断其是否是一个有效的二叉搜索树。

  • 分析,使用中序遍历,每次看待加入的值是否比前一个大,如果小于等于前一个数,就不是二叉搜索树。
  • 解题模板:
def dfs(root):
		if not root:return 
		dfs(root.left)
		isMatch()
		dfs(root.right)

99. 恢复二叉搜索树

题目:二叉搜索树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。

  • 分析:二叉搜索树两个节点被交换,使用中序遍历存储节点值,可以找到错误的两个节点。再对二叉树进行中序遍历,如果当前值和排序后的存储值对不上,那么说明这就是一个错误的节点。我们只需要改变这个节点的值就行了。
  • 解题模板:
        def insort(root):#定义一个中序遍历
            if not root: return 
            insort(root.left)
            res.append(root.val)
            insort(root.right)

        def change(root):#定义一个遍历交换
            if not root: return 
            if root.val in err:
                root.val = err[0] if root.val == err[1] else err[1]
            change(root.left)
            change(root.right)

        insort(root)#中序遍历,得到序列数组
        change()

100. 相同的树

题目:给定两个二叉树,编写一个函数来检验它们是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

  • 分析:相同的树即当前节点和子节点都是相同的。需要对节点分情况讨论:
    • 两个节点都为空:True
    • 两个节点有一个为空:False
    • 两个节点都存在,但是值不相等:False
    • 两个节点存在,且值相等。接着递归判断这两个节点的左 和 这两个节点的右。
  • 解题模板:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        if not p and not q:return True#q p当前都为空
        if not p or not q: return False#q p 当前有一个为空,一个不为空
        if p.val != q.val:return False#两者当前值不相等,返回Fasle
        #还有一种情况就是两者都不为空,并且值相等,需要左右向下寻找
        #只有所有的都为True,最中结果才会为True
        return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)

101. 对称二叉树

题目:给定一个二叉树,检查它是否是镜像对称的。例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

  • 分析:判断两个节点是否相等,有四种情况:
    • 两个都为空:则相等
    • 一个为空,另一个不为空。
    • 两个都不为空,但是值不相等。
    • 两个都有值,并且值相等,可以继续向下判断,由于是对称,所以左左对应右右,左右对应右左。
  • 解题模板:
def solve(l_root, r_root):
      if type(l_root) != type(r_root):#如果左右节点类型不一致,直接False
          return False
      elif l_root == None:#节点类型一致中,分null 和数值两种情况,当为null的时候,不需递归,直接返回
          return True
      elif l_root.val == r_root.val:#当两者的值相等的时候,可以递归,左节点的左子树与右节点的右子树
          return solve(l_root.left, r_root.right) and solve(l_root.right, r_root.left)
      else:
          return False

104. 二叉树的最大深度

题目:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。

  • 分析:从底部回归,非空节点往上走一次,深度值加一。
  • 解题模板:
def solve(root:TreeNode):
      if not root:#节点为空,返回当前层的深度
          return 0
      else:#由于自底向上,每一个节点深度都等于 max(左边最大深度, 右边最大深度)
          return max(solve(root.right) + 1, solve(root.left) + 1)#返回的时候每往上走一层就加一

105. 从前序与中序遍历序列构造二叉树

题目:根据一棵树的前序遍历与中序遍历构造二叉树。

  • 分析:

    • 1.前序中左起第一位1肯定是根结点,我们可以据此找到中序中根结点的位置node;
    • 2.中序中根结点左边就是左子树结点,右边就是右子树结点,即 inorder[左子树结点,根结点,右子树结点],我们就可以得出左子树结点个数为int left = rootin - leftin;;
    • 3.前序中结点分布应该是:[根结点,左子树结点,右子树结点];
    • 4.根据前一步确定的左子树个数,可以确定前序中左子树结点和右子树结点的范围;
  • 解题模板:

def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
    if not preorder or not inorder: return #当前数组内没有值,返回空
    index = inorder.index(preorder[0])#从preorder中找到根节点在inorder内的位置
    T =  TreeNode(preorder[0])#赋值为一个子树的根节点
    T.left = self.buildTree(preorder[1: 1+index], inorder[ :index])#找到左子树
    T.right = self.buildTree(preorder[index+1: ], inorder[index+1:])#找到右子树
    return T

106. 从中序与后序遍历序列构造二叉树

题目:根据一棵树的中序遍历与后序遍历构造二叉树。注意:你可以假设树中没有重复的元素。
-分析:和上面的105. 从前序与中序遍历序列构造二叉树一样,我们要做的就是找到头节点。后序遍历的头节点在尾部,后序遍历的尾部头节点在中序遍历中就可以将列表分为左右子树。
-解题思路:

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        if not inorder or not postorder: return
        index = inorder.index(postorder[-1])
        root = TreeNode(postorder[-1])
        root.left = self.buildTree(inorder[:index], postorder[:index])
        root.right = self.buildTree(inorder[index+1: ], postorder[index:-1])
        return root
  • 小结一下

  • 前+后
    首先我们可以显然知道当前根节点为pre[pre_start],并且它在后序中的位置为post_end,因此这里我们需要找到能区分左右子树的节点。我们知道左子树的根节点为pre[pre_start+1],因此只要找到它在后序中的位置就可以分开左右子树(index的含义)

  • 前+中
    首先我们可以显然知道当前根节点为pre[pre_start],只用找出它在中序中的位置,就可以把左右子树分开(index的含义)

  • 中+后
    首先我们可以显然知道当前根节点为post[post_end],只用找出它在中序中的位置,就可以把左右子树分开(index的含义)

108. 将有序数组转换为二叉搜索树

题目:将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

  • 分析,建立二叉搜索树的第一步就是找根节点,一个升序列表的根节点在中间。中间值将列表分为左右子树。
  • 解题模板:
def inorder(nums, start, end):
    if start > end: return None
    mid = start + (end - start) // 2#找到中间值得得索引
    root = TreeNode(nums[mid])#最中间值建立节点
    root.left = inorder(nums, start, mid-1)#左子树连接
    root.right = inorder(nums, mid+1, end)#右子树连接
    return root

109. 有序链表转换二叉搜索树

题目:给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。

  • 分析:链表中的中位数使用快慢指针进行查找。递归调用,找到分别构建左子树右子树。
  • 解题模板:
def sortedListToBST(self, head: ListNode) -> TreeNode:
    def find_mid(pre, post):#使用快慢指针,找到中间的那个数
        first = pre
        second = pre
        while second != post and second.next != post:#当快指针当前和下一个节点不为尾节点
            first = first.next
            second = second.next.next
        return first

    def helper(head, tail):#递归调用
        if head == tail: return #弹出条件
        node = find_mid(head, tail)
        root = TreeNode(node.val)
        root.left = helper(head, node)#左边递归
        root.right = helper(node.next, tail)#右边递归
        return root
    return helper(head, None)

110. 平衡二叉树

题目:给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

  • 分析:需要对节点进行遍历,这里选择前序遍历。每一个节点要判断其左右两端的深度差是否大于1。从底向上以此判断。
  • 解题模板:使用双递归,dfs递归是为了计算每一个节点的高度值=左右节点高度的最大值加一。之后就是再次递归,判断每一个节点的左右深度是否满足要求,满足就继续向上递归看其是否满足高度差条件。
def isBalanced(self, root: TreeNode)-> int:
    def dfs(root):#计算当前节点额深度
        if not root:return 0
        lheight = dfs(root.left)+1
        rheight = dfs(root.right)+1
        return max(lheight, rheight)
        
    def isValid(root):#判断该节点的左右深度是否符合条件
        if not root:return True
        return True if abs(dfs(root.left)-dfs(root.right))<2 else False
    
    if not root:return True
    else:#从下到上,判断左右节点是否满足高度差的条件,并且继续左右递归
        return self.isBalanced(root.left) and self.isBalanced(root.right) and isValid(root)

111. 二叉树的最小深度

题目:给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

  • 分析:求最小深度,那么应该是 min(dfs(root.letf), dfs(root.right))+1。这道题会有一种特殊情况就是单变二叉树,这样的话就不能简单的使用ans = min(solve(root.left), solve(root.right)) + 1,因为单边,另一边的值一直为1,这样求min,算出来的最小深度不对,所以只有分开单边的情况讨论。即一边为空,就递归另一边。
  • 三种情况:
    • 单边的left为空:递归right
    • 单边的right为空,递归left
    • 两边都不为空,递归 letf 和 righ, 取其中的最小值
  • 解题模板;
def solve(root):
    if not root: return 0
    if not root.right:#这是两种单边的情况,需要单独谈论,单边就不需比较,只对另一边求解。
        ans = solve(root.left)+1
    elif not root.left:
        ans = solve(root.right)+1
    else:#节点左右两边都有值,所以需要比大小
        ans = min(solve(root.left), solve(root.right)) + 1
    return ans

112. 路径总和

题目:给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

  • 分析:从根节点出发,到叶子节点进行累加求和,到了叶子节点后如果值相匹配,那么就返回True。
  • 解题模板:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
    if not root: return False
    if sum == root.val and not root.right and not root.left: return True
    return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum- root.val)

注意:达到叶子节点的时候的判断条件,左右为空,当前有值。

113. 路径总和 II

题目:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

  • 分析:这里比上一道多一个步骤就是要记录沿途的所有节点值,但是整体还是不变。
  • 解题模板:
 res, path = [], []
 def recur(root, tar):
     if not root: return
     path.append(root.val)#每次加入到临时路径中
     tar -= root.val
     if tar == 0 and not root.left and not root.right:#找到一条合法路径
         res.append(list(path))
     recur(root.left, tar)
     recur(root.right, tar)
     path.pop()#返回的时记得要弹出
     
 recur(root, sum)

114. 二叉树展开为链表

题目:给定一个二叉树,原地将它展开为一个单链表。

  • 分析:这道题开始也没想明白怎么弄,只知道可以前序遍历之后,再重新组合成一个右子树,同时左子树清零。
  • 思路:
    1.将左子树插入到右子树的地方
    2.将原来的右子树接到左子树的最右边节点
    3.考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
  • 解题模板
 def solve(root):
     head = root
     if head.left:#如果左子树存在
         node = head.left
         while node.right:#找到左子树的最右节点
             node = node.right
         node.right = head.right#将左子树的最右连接一个右子树
         new_right = head.left#断开左子树
         head.left = None
         head.right = new_right#将左子树移动到原来右子树的位置
         
 while root:
     solve(root)
     root = root.right

116. 填充每个节点的下一个右侧节点指针

题目:给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

  • 分析:这道题的连接思路为,每一行其实是有三个点, root.left, root.right, 还有一个点为root.next下面的 root.next.left。有了上述的三个点我们才能够将这每一行连接起来。但是有时候 root.next不存在(每一行的末尾),所以就需要对其进行 是否存在的判断。
  • 解题模板
def solve(r_left, r_right, pre_right):
    if not r_left or not r_right: #如果左右节点为空,直接返回
        return
    r_left.next = r_right#不为空则先连接从左到右
    if pre_right != None:#如果上一层的next不为空
        r_right.next = pre_right.left#将本层的右节点与上一层next的左节点相连
    #本层的连接已经完成,接下来就是需要递归,首先就判断子节点是否存在,不存在直接返回,
    #也可以直接递归,因为递归中第一行已经对是否存在进行了判断
    if not r_left.left:
        return
    else:
        solve(r_left.left, r_left.right, r_left.next)#注意这里的上一层next,为left的next
        solve(r_right.left, r_right.right,r_right.next)#这里上一层的next为right的next

这里还有一种简洁的写法:

##一种简洁的写法
def connect(self, root: 'Node') -> 'Node':
        if not root:
            return 
        if root.left:
            root.left.next = root.right
            if root.next:
                root.right.next = root.next.left
        self.connect(root.left)
        self.connect(root.right)
        return root

124. 二叉树中的最大路径和

题目:给定一个非空二叉树,返回其最大路径和。本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

  • 分析:我们在计算某一段的最大值的时候,例如示例一:是取得 2+1+3 = 6。但是我们在往更上层返回值的时候,由于本题要求的是点到点,不能有岔路,但是如果返回一个有岔路的树,就不对。所以这里只能返回本节点和左右节点中的最大值。
  • 难点:上面这一点在我做这道题的时候想怎么去设置返回值,如果设置max(左,右) + root作为返回值,那么假如是示例一那样的形状,需要左中右三个点的值,就不成立。但是返回三个点的值就违背了题目的意思。最后看了题解才知道,只需要设定一个全局的最大值,每次更新这个最大值就可以了,返回的话就只返回max(左,右) + root。这里的root是必须要的。
  • 注意:有一个需要注意的地方就是,有的节点值是负数,我们在返回的时候,还需要控制一下负数的情况,负数在这里就是返回的0, 因为和一个 0 取max,如果返回值和小于0,经过max后等于0,也就相当于不取这个值的意思。
  • 解题模板:
class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        self.max = -float('inf')
        def solve(root):
            if not root:return 0
            left =  solve(root.left) #得到左子树的最大值
            right = solve(root.right) #得到右子树的最大值
            self.max = max(self.max, left + right + root.val)#记录当前的值,为一个左中右
            return max(0, max(left, right) + root.val)#需要经过根节点才能往上递归
        solve(root)
        return self.max

129. 求根到叶子节点数字之和

题目:给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。例如,从根到叶子节点路径 1->2->3 代表数字 123。计算从根到叶子节点生成的所有数字之和。

  • 分析:这就像等于上面的113. 路径总和 II但是这里每一步取值的时候我们需要乘以一个10,找到叶子节点的时候就加入到累加的输出中。使用DFS, 计算每一个节点的子节点能够得到的数值,左右相加得到为根节点最多的数值。

  • 解题模板:

def dfs(root, res):
      if not root: return 0
      if not root.left and not root.right:
          return res*10 + root.val
      left = dfs(root.left, res*10 + root.val)
      right = dfs(root.right, res*10 + root.val)
      return left + right#返回的是左右节点相加能够得到的和,也就是该节点能够得到的值

130. 被围绕的区域

题目:给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

  • 分析:在寻找连通区域的时候可以使用DFS和BFS,这里就贴下DFS的查找过程
  • 解题模板:
def dfs(i, j):
     board[i][j] = "B"
     for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
         tmp_i = i + x
         tmp_j = j + y
         if 1 <= tmp_i < row and 1 <= tmp_j < col and board[tmp_i][tmp_j] == "O":
             dfs(tmp_i, tmp_j)

133. 克隆图

题目:给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。

class Node {
    public int val;
    public List<Node> neighbors;
}
  • 分析:连通的无向图,我们可以使用DFS和BFS赋值,这里考试使用DFS,注意在克隆的时候需要注意不要陷入一个循环,所以这里多接了一个字典记录是否已经克隆过了,防止陷入循环。
  • 解题模板
def dfs(node):
    if not node:
        return 
    if node  in visited:
        return visited[node]
    clone = Node(node.val, [])#建立一个头clone
    visited[node] = clone #将克隆的头加入已经访问过的表,防止重复访问。
    for item in node.neighbors:#对于头节点的每一个相邻节点,加入到克隆的头的邻居中
        clone.neighbors.append(dfs(item))#邻居中加入的是每一个邻接点的克隆,在使用一次DFS
    return clone#返回的是克隆的头节点 

199. 二叉树的右视图

题目:给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

  • 分析:每次设置一个depth深度值,每次递归的时候深度值加一,如果当前列表内长度小于深度值,就加入列表,因为这里时右子树的DFS在前面,所以首先加入的时右子树,如果右子树不存在,才会加入左子树
  • 解题模板;
class Solution:
    def rightSideView(self, root: TreeNode) -> List[int]:
        res = []
        def dfs(root, depth):
            if not root:return 
            if len(res) < depth:#这里限定了每一层只能加入一个节点
                res.append(root.val)
            dfs(root.right, depth+1)#先加入的是右子树的右节点
            dfs(root.left, depth+1)
        dfs(root,1)
        return res

200. 岛屿数量

题目:给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。

  • 分析:首先找到一个点1, 以此扩散,将与其连接的所以1变化成0。然后岛屿数加一。在寻找是否还有1.遍历的方法有DFS和BFS。这里贴一下DFS.
  • 解题模板:
class Solution:
    def numIslands(self, grid: [[str]]) -> int:
        def dfs(grid, i, j):
            if not 0 <= i < len(grid) or not 0 <= j < len(grid[0]) or grid[i][j] == '0': return
            grid[i][j] = '0'
            dfs(grid, i + 1, j)
            dfs(grid, i, j + 1)
            dfs(grid, i - 1, j)
            dfs(grid, i, j - 1)
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    dfs(grid, i, j)
                    count += 1
        return count

207. 课程表

题目:你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

  • 分析:可以使用BFS的入度法判断有无环图,这里使用的是DFS判断是否存在有向无环图

    • 借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:
    • 未被 DFS 访问:i == 0;
    • 已被其他节点启动的 DFS 访问:i = -1;
    • 已被当前节点启动的 DFS 访问:i = 1。
  • DFS 流程:

    • 终止条件:当 flag[i] == -1,说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 True。
    • 当 flag[i] == 1,说明在本轮 DFS 搜索中节点 i 被第 2 次访问,即 课程安排图有环 ,直接返回 False。
    • 将当前访问节点 i 对应 flag[i] 置 1,即标记其被本轮 DFS 访问过;
    • 递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 False;
    • 当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 −1 并返回 True。
      若整个图 DFS 结束并未发现环,返回 True。
  • 解题模板:

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        def dfs(i, adjacency, flags):
            if flags[i] == -1:return True#如果之前其他起始节点已经访问过
            elif flags[i] == 1:return False#如果本次节点已经访问过
            flags[i] = 1 # 如果该节点从未被访问过,本次访问,设置 1
            for j in adjacency[i]:#对节点i的相连节点进行DFS
                if not dfs(j, adjacency, flags):return False
            #如果与之所有相连的节点都没有发现环,那么本节点设置为 -1,为之后的节点表示,这个节点已经被访问过,没有环出现
            flags[i] = -1
            return True

        adjacency = [[] for _ in range(numCourses)]
        flags = [0 for _ in range(numCourses)]
        for cur, pre in prerequisites:#创建一个图表,每一个节点之后包含所连的节点
            adjacency[pre].append(cur)
        
        for i in range(numCourses):#对每一个节点进行DFS判断
            if not dfs(i, adjacency, flags): return False
        return True

210. 课程表 II

题目:现在你总共有 n 门课需要选,记为 0 到 n-1。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

  • 分析:这道题还可以使用DFS,进行环图的判断,设定标志2表示此点当前DFS被访问过, 如果为1表示被其他DFS访问过, 如果为0表示没有被访问过。如果找到了一个环,直接返回False,否则最后输出res内保存的。可能是否已经遍历过使用
class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        res = []
        visited = [0] * numCourses
        adjacent = [[] for _ in range(numCourses)]

        def dfs(i):#判断是否能够遍历完,不进入环
            if visited[i] == 1:
                return False
            if visited[i] == 2:
                return True
            visited[i] = 1
            for j in adjacent[i]:
                if not dfs(j):
                    return False

            visited[i] = 2
            res.append(i)
            return True
        for cur, pre in prerequisites:#建立图
            adjacent[cur].append(pre)
        for i in range(numCourses):#查找每一个课作为开始是否满足要求
            if not dfs(i):
                return []
        return res

257. 二叉树的所有路径

题目:给定一个二叉树,返回所有从根节点到叶子节点的路径。说明: 叶子节点是指没有子节点的节点。

  • 分析:使用DFS,从根节点往下面回溯寻找每一条链路,并且将其加入字符串,到达叶子节点的时候,就加入到输出out。
  • 解题模板:
def backtrack(root, res):
     if not root: return 
     if not root.left and not root.right:
         res +=  '->' + str(root.val)
         out.append(res[2:])
         return 

     if root.left:
         backtrack(root.left, res + '->' + str(root.val))
     if root.right:
         backtrack(root.right, res + '->' + str(root.val))

329. 矩阵中的最长递增路径

题目:给定一个整数矩阵,找出最长递增路径的长度。对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

  • 分析:可以每次寻找四个方向比当前值小的方向进行DFS,最后选择后一个最大的路径。注意:这里需要保存当前走过的路径,否则会走一些重复的路,导致超时。
  • 解题模板:
def dfs(i,j):
    if (i,j) in memo:
        return memo[(i,j)]
    max_len = 0
    for di, dj in [(i+1, j), (i-1, j), (i, j+1), (i, j-1)]:
        if 0 <= di < m and 0 <= dj < n and matrix[di][dj] < matrix[i][j]:
            max_len = max(max_len, dfs(di,dj))#从四个方案中找一个最长的
    memo[(i,j)] = max_len + 1#本次的值,比之前的值多一个
    return memo[(i,j)] #返回当前的最大长度

337. 打家劫舍 III

题目:在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

  • 分析:这道题的取法:
    1. 爷爷节点取了值,就只能从孙子节点中取最大,然后相加
    2. 爷爷节点没有取,就只能从两个儿子节点的中取最大和
  • 这里设定withroot:偷本节点; without:本次不偷; 每次的返回值为[当前不偷, 当前偷与不偷的最大值]。这里这么设定的原因是,加入爷爷节点不偷,是两个儿子收益最大值,但是这两个儿子节点本次不一定会偷,可能偷/可能不偷。
  • 解题模板:
def solve(root):
    if not root:return [0,0]
    left = solve(root.left)#返回的是一个列表[不偷左, 偷左与不偷左的最大值]
    right = solve(root.right)
    without = left[1] + right[1]#本次不偷,就是取左右儿子得到的最大值和(儿子参与/儿子不参与)
    withroot = root.val + left[0] + right[0]#本次偷,那么左右儿子不能参与计算
    return[without, max(withroot, without)]#精髓所在[当前不偷,取左右最大和;, 当前偷/不偷能获得的最大值]

394. 字符串解码

题目:给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

  • 分析:
    1. 字符串的重复个数可能是大于十的,所以在计数的时候需要累加
    2. 字符可能是小写,也可能是大写,小写和大写中间的字符还包括了 " [ ", " ] "。
    3. 左括号开始递归,右括号结束递归。
class Solution:
    def decodeString(self, s: str) -> str:
        def dfs(s, i):
            res, multi = "", 0
            while i < len(s):
                if '0' <= s[i] <= '9':
                    multi = multi * 10 + int(s[i])#倍数累计
                elif s[i] == '[':
                    i, tmp = dfs(s, i + 1)
                    res += multi * tmp#将右括号返回括号内的值,进行字符串相加
                    multi = 0#倍数清零
                elif s[i] == ']':#右括号,开始返回括号内的字符和当前的索引值,相当于跳到右括号之后
                    return i, res
                else:
                    res += s[i]#括号内的字符相加
                i += 1
            return res
        return dfs(s,0)

559. N叉树的最大深度

题目:给定一个 N 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

  • 分析:类似二叉树的最大深度,这里也是一样的DFS
  • 解题模板:
class Solution:
    def maxDepth(self, root: 'Node') -> int:
        if not root: return 0
        def solve(root):
            ans = 0
            if not root.children: return 1
            for chl in root.children:
                ans = max(ans, self.maxDepth(chl)+1)
            return ans
        return solve(root)  

733. 图像渲染

题目:有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。最后返回经过上色渲染后的图像。

  • 分析:和之前的岛屿那些题目类似,可以BFS,也可以DFS.注意:如果新颜色等于旧颜色,直接返回。
  • 解题模板:
class Solution:
    def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]: 
        row, col = len(image), len(image[0])
        p = image[sr][sc]
        if p == newColor:
            return image

        def dfs(x: int, y: int) -> None:
            if x in (-1, row) or y in (-1, col) or image[x][y] != p:
                return 
            image[x][y] = newColor

            for i in (-1,1):
                dfs(x+i, y)
                dfs(x, y+i)

            return image
        
        return dfs(sr, sc)

785. 判断二分图

题目:给定一个无向图graph,当这个图为二分图时返回true。如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。

  • 二分的常用方法就是染色法。初始的时候选择一个点进行染色1,把与之相邻的点染成另一种不一样的颜色2。之后对色2的相邻的未染色点染色1,如此重复。如果在染色的过程中发现待染色1的点已经被染色1,继续递归。如果待染色1的点为色2,那么说明此时不是一个二分图,直接返回False。
  • 解题模板:
class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        vis = [0] * len(graph)

        def dfs(pos, color):
            vis[pos] = color
            for i in graph[pos]:
                # 颜色相同 or (未访问 且 继续深搜为False)
                # 可直接返回False
                if vis[i] == color or not (vis[i] or dfs(i, -color)):
                    return False
            return True

        # 不一定为联通图,需遍历每一个节点
        for i in range(len(graph)):
            if not vis[i] and not dfs(i, 1):
                return False
        return True

841. 钥匙和房间

题目:有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,…,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,…,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。最初,除 0 号房间外的其余所有房间都被锁住。你可以自由地在房间之间来回走动。如果能进入每个房间返回 true,否则返回 false。

  • 分析:这道题还可使使用BFS. 这里说一下DFS:从第一个房间进去,拿到钥匙,如果钥匙还可以开其他没有进去过的门,就去把它打开。层层深入,找到没有可以进入的门,这个时候回溯回来,看其他的钥匙。最后统计打开了多少道门。
  • 解题模板:
class Solution:
    def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
        visited = {0}
        def dfs(room_index,visited):
            visited.add(room_index)
            for key in rooms[room_index]:
                if key not in visited: dfs(key,visited)
        dfs(0,visited)
        return len(visited) == len(rooms)
题目描述: 给定一个 $N \times M$ 的矩阵,其中 "." 表示水洼,"W" 表示水。请计算有多少个水洼。 解题思路: 这是一道非常经典的搜索题目。我们可以使用 DFS 或 BFS 进行求解。 首先,我们需要遍历整个矩阵,当我们遇到一个 "." 时,我们就从该点开始向四周搜索,将所有相邻的 "." 变为 "W" ,并继续向下搜索。每次搜索完毕后,我们就可以找到一个完整的水洼,计数器加一。最后,当我们遍历完整个矩阵后,就可以得到所有的水洼数量。 代码实现: 使用 DFS 进行搜索: ```c++ #include <iostream> using namespace std; const int maxn = 110; char field[maxn][maxn]; bool vis[maxn][maxn]; int n, m; void dfs(int x, int y) { vis[x][y] = true; for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int nx = x + dx, ny = y + dy; if (nx >= 0 && nx < n && ny >= 0 && ny < m && !vis[nx][ny] && field[nx][ny] == '.') { dfs(nx, ny); } } } } int main() { cin >> n >> m; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> field[i][j]; } } int res = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (!vis[i][j] && field[i][j] == '.') { dfs(i, j); res++; } } } cout << res << endl; return 0; } ``` 使用 BFS 进行搜索: ```c++ #include <iostream> #include <queue> using namespace std; const int maxn = 110; char field[maxn][maxn]; bool vis[maxn][maxn]; int n, m; void bfs(int x, int y) { queue<pair<int, int>> q; q.push(make_pair(x, y)); vis[x][y] = true; while (!q.empty()) { int cx = q.front().first, cy = q.front().second; q.pop(); for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int nx = cx + dx, ny = cy + dy; if (nx >= 0 && nx < n && ny >= 0 && ny < m && !vis[nx][ny] && field[nx][ny] == '.') { q.push(make_pair(nx, ny)); vis[nx][ny] = true; } } } } } int main() { cin >> n >> m; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> field[i][j]; } } int res = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (!vis[i][j] && field[i][j] == '.') { bfs(i, j); res++; } } } cout << res << endl; return 0; } ``` 时间复杂度: 两种方法的时间复杂度均为 $O(NM)$,其中 N 和 M 分别为矩阵的行数和列数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值