Leetcode刷题笔记18:二叉树5(513.找树左下角的值-112、113路径总和i、ii -106、105遍历序列构造二叉树)

86 阅读7分钟

导语

leetcode刷题笔记记录,本篇博客记录二叉树部分的题目,主要题目包括:

  • 513 找树左下角的值
  • 112 路径总和
  • 113 路径总和ii
  • 106 从中序与后序遍历序列构造二叉树
  • 105 从前序与中序遍历序列构造二叉树

Leetcode 513 找树左下角的值

题目描述

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。

示例 1:

输入: root = [2,1,3]
输出: 1

解法

这道题目使用层次遍历最简单直观,只需要返回最后一层的第一个值即可,代码如下:

from collections import deque

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        queue = deque()
        if root:
            queue.append(root)
        result = root.val
        while queue:
            size = len(queue)
            items = []
            for _ in range(size):
                node = queue.popleft()
                items.append(node.val)
                if node.left: queue.append(node.left)
                if node.right: queue.append(node.right)
            result = items[0]
        return result

同时,也可以使用递归来求解,在进行递归时,仍需要使用回溯的思想,由于不需要对中间节点进行任何操作,所以任意一种递归的遍历顺序均可以。

  1. 递归函数的参数和返回值

参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。不需要返回值。本题还需要类里的两个全局变量,max_depth用来记录最大深度,result记录第一个到达最大深度的节点(就是我们要找的这个节点)的数值。

  1. 终止条件

当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。

  1. 单层递归的逻辑

在找最大深度的时候,递归的过程中依然要使用回溯

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        self.max_depth = float('-inf')
        self.result = None
        self.traversal(root, 0)
        return self.result

    def traversal(self, node, depth):
        if not node.left and not node.right:
            if depth > self.max_depth:
                self.max_depth = depth
                self.result = node.val
            return
        
        if node.left:
            depth += 1
            self.traversal(node.left, depth)
            depth -= 1
        if node.right:
            depth += 1
            self.traversal(node.right, depth)
            depth -= 1

Leetcode 112/113 路径总和i、ii

题目描述

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。 叶子节点 是指没有子节点的节点。

示例 1:

输入: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出: true
解释: 等于目标和的根节点到叶节点路径如上图所示。

解法

使用递归和回溯进行求解,由于这里只需要判断是否存在,所以需要返回一个布尔值。对于递归函数什么时候需要返回值?什么时候不需要返回值?代码随想录总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)

所以本题目的递归三部曲如下:

  1. 递归函数的参数和返回类型:需要一个树的根节点和一个计数器用于计算目标和,返回值为布尔值;
  2. 终止条件:让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。如果遍历到了叶子节点,count不为0,就是没找到。
  3. 单层逻辑:不判断空节点,如果递归函数返回了True则可以直接返回,表明找到了。
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        return self.traversal(root, targetSum-root.val)

    def traversal(self, node, count):
        if node.left == None and node.right == None:
            return count == 0
        if node.left:
            count -= node.left.val
            if self.traversal(node.left, count):
                return True
            count += node.left.val
        if node.right:
            count -= node.right.val
            if self.traversal(node.right, count):
                return True
            count += node.right.val
        return False

113 路径总和ii

题目描述

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。叶子节点 是指没有子节点的节点。

示例 1:

输入: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出: [[5,4,11,2],[5,8,4,5]]

解法

与112类似,只不过这里更加复杂,需要保留路径,因此定义两个全局变量用于存储当前的路径和所有符合条件的结果。路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!

class Solution:
    def __init__(self):
        self.result = []
        self.path = []


    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        self.result.clear()
        self.path.clear()
        if root is None:
            return self.result
        self.path.append(root.val)
        self.traversal(root, targetSum - root.val)
        
        return self.result


    def traversal(self, cur, count):
        if cur.left is None and cur.right is None:
            if count == 0:
                self.result.append(self.path[:])
            return
        
        if cur.left:
            self.path.append(cur.left.val)
            count -= cur.left.val
            self.traversal(cur.left, count)
            count += cur.left.val
            self.path.pop()

        if cur.right:
            self.path.append(cur.right.val)
            count -= cur.right.val
            self.traversal(cur.right, count)
            count += cur.right.val
            self.path.pop()

        return 

Leetcode 106/105 构造二叉树

题目描述

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这棵 二叉树 。

示例 1:

输入: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出: [3,9,20,null,null,15,7]

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorder 和 postorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder 中
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

解法

根据两个顺序构造一个唯一的二叉树,以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。流程如图(参考代码随想录):

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

递归逻辑如下列代码段所示:

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        # 第一步:特殊情况,处理空的情况
        if not inorder:
            return None

        # 第二步:后序遍历的最后一个节点就是根节点
        root_value = postorder[-1]
        root = TreeNode(root_value)

        # 第三步:寻找切割点
        sep_index = inorder.index(root_value)


        # 第四步:切割中序数组,得到左中序、右中序
        left_inorder = inorder[:sep_index]
        right_inorder = inorder[sep_index+1:]

        # 第五步:切割postorder数组,得到左后序、右后序
        # 重点:中序数组大小一定与后序数组大小相同的
        left_postorder = postorder[:len(left_inorder)]
        right_postorder = postorder[len(left_inorder):len(postorder)-1]

        # 第六步:递归
        root.left = self.buildTree(left_inorder, left_postorder)
        root.right = self.buildTree(right_inorder, right_postorder)


        # 第七步:返回答案
        return root

类似的,我们可以得到根据前序和中序的序列获得二叉树的方法,具体代码如下:

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        # 第一步:特殊情况,处理空的情况
        if not inorder:
            return None

        # 第二步:后序遍历的最后一个节点就是根节点
        root_value = preorder[0]
        root = TreeNode(root_value)

        # 第三步:寻找切割点
        sep_index = inorder.index(root_value)


        # 第四步:切割中序数组,得到左中序、右中序
        left_inorder = inorder[:sep_index]
        right_inorder = inorder[sep_index+1:]

        # 第五步:切割postorder数组,得到左后序、右后序
        # 重点:中序数组大小一定与后序数组大小相同的
        left_preorder = preorder[1:len(left_inorder)+1]
        right_preorder = preorder[len(left_inorder)+1:]

        # 第六步:递归
        root.left = self.buildTree(left_preorder, left_inorder)
        root.right = self.buildTree(right_preorder, right_inorder)


        # 第七步:返回答案
        return root