leetcode-树(二)

目录

257. 二叉树的所有路径

113. 路径总和 II

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

437. 路径总和 III

235. 二叉搜索树的最近公共祖先

98. 验证二叉搜索树

450. 删除二叉搜索树中的节点

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

230. 二叉搜索树中第K小的元素

236. 二叉树的最近公共祖先


257. 二叉树的所有路径

https://leetcode-cn.com/problems/binary-tree-paths/

给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

示例:

输入:

   1
 /   \
2     3
 \
  5

输出: ["1->2->5", "1->3"]

解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3

题解

一:DFS,递归。

class Solution(object):
    def binaryTreePaths(self, root):
        """
        :type root: TreeNode
        :rtype: List[str]
        """
        res, rec = [], ""
        if not root:
            return res
        self._helper(root, rec, res)
        return res
        
    def _helper(self, node, rec, res):
        if not node.left and not node.right:
            res.append(rec+str(node.val))
            return
        if node.left:
            self._helper(node.left, rec+str(node.val)+"->", res)
        if node.right:
            self._helper(node.right, rec+str(node.val)+"->", res)

113. 路径总和 II

https://leetcode-cn.com/problems/path-sum-ii/

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

说明: 叶子节点是指没有子节点的节点。

示例:
给定如下二叉树,以及目标和 sum = 22,

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1
返回:

[
   [5,4,11,2],
   [5,8,4,5]
]

题解

一:DFS,递归,感觉目前遇到的树类型的题目思路都差不多。需要记录值,最后还要释放,释放的时候由于python是引用,所以值拷贝放入的最终结果,看代码。

class Solution(object):
    def pathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: List[List[int]]
        """
        res, rec = [], []
        if not root:
            return res
        self._helper(root, rec, sum, res)
        return res

    def _helper(self, node, rec, target, res):
        if not node.left and not node.right:
            if target == node.val:
                rec.append(node.val)
                res.append(rec[:])
                rec.pop()
            return 
        rec.append(node.val)
        if node.left:
            self._helper(node.left, rec, target - node.val, res)
        if node.right:
            self._helper(node.right, rec, target - node.val, res)
        rec.pop()

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

https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/

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

示例 1:输入: [1,2,3]
    1
   / \
  2   3
输出: 25
解释:从根到叶子节点路径 1->2 代表数字 12。从根到叶子节点路径 1->3 代表数字 13。因此,数字总和 = 12 + 13 = 25。
示例 2:输入: [4,9,0,5,1]
    4
   / \
  9   0
 / \
5   1
输出: 1026
解释:从根到叶子节点路径 4->9->5 代表数字 495。从根到叶子节点路径 4->9->1 代表数字 491。从根到叶子节点路径 4->0 代表数字 40。因此,数字总和 = 495 + 491 + 40 = 1026。

题解

一:大体相同的思路,这边接住了库函数,str和int。

class Solution(object):
    def sumNumbers(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root:
            return 0
        res, rec = [0], 0
        self._helper(root, res, rec)
        return res[0]

    def _helper(self, node, res, rec):
        if not node.left and not node.right:
            res[0] = res[0] + rec * 10 + node.val
            return 
        rec = rec * 10 + node.val
        if node.left:
            self._helper(node.left, res, rec)
        if node.right:
            self._helper(node.right, res, rec)

437. 路径总和 III

https://leetcode-cn.com/problems/path-sum-iii/

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。和等于 8 的路径有:

1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

题解

一:起始点的三种情况:当前节点,当前节点的左孩子,当前节点的右孩子,self._helper(root, sum, res),以当前节点的左右孩子(即不包括当前节点)做起始点,这么看,只是缺少了以根节点作为起始节点的情况,故单独给出。

class Solution(object):
    def pathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: int
        """
        if not root:
            return 0
        # 以root为根的符合题目要求的路径数
        self._sum(root, sum, res)
        # 以root的左右孩子为根的符合题目要求的路径数
        self._helper(root, sum, res)
        return res[0]
    # 递归遍历每一个节点,看看他们左右孩子为根的符合题目要求的路径数
    def _helper(self, node, target, res):
        if not node:
            return 
        self._sum(node.left, target, res) 
        self._sum(node.right, target, res)
        self._helper(node.left, target, res)
        self._helper(node.right, target, res)
    
    # 以node为根的符合题目要求的路径数,必须以node为起点
    def _sum(self, node, target, res):
        if not node:
            return 
        target -= node.val
        if target == 0:
            res[0] += 1
        if node.left:
            self._sum(node.left, target, res)
        if node.right:
            self._sum(node.right, target, res)

235. 二叉搜索树的最近公共祖先

https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8。输出: 6 。解释: 节点 2 和节点 8 的最近公共祖先是 6。

题解

一:二叉搜索树,是一个二叉树(不一定是一个完全的二叉树),每个节点的键值大于左孩子;每个节点的键值小于右孩子;以左右孩子为根的子树仍为二分搜索树,通过DFS,若我们遇到的节点的键值位于p和q键值的中间,这该节点就是最近的公共祖先,若小于p和q的最小值,则其最近的公共祖先必在其右子树的节点上,反之若大于p和q的最大值,则其最近的公共祖先必在其左子树的节点上。

class Solution(object):
    def lowestCommonAncestor(self, root, p, q):
        """
        :type root: TreeNode
        :type p: TreeNode
        :type q: TreeNode
        :rtype: TreeNode
        """
        if not root:
            return None
        if p.val > q.val:
            p, q = q, p
        return self._helper(root, p, q)
        
    def _helper(self, node, p, q):
        if not node:
            return None
        if p.val <= node.val <= q.val:
            return node
        if node.val > q.val:
            return self._helper(node.left, p, q)
        if node.val < p.val:
            return self._helper(node.right, p, q)

98. 验证二叉搜索树

https://leetcode-cn.com/problems/validate-binary-search-tree/

给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征:节点的左子树只包含小于当前节点的数。节点的右子树只包含大于当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。
示例 1:

输入:
    2
   / \
  1   3
输出: true
示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false。解释: 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。

题解

一:暴力法,时间复杂度O(n^2),对每一个节点,查看其左子树是否都小于该节点,查看右子树是否都大于该节点

二:中序遍历法,时间复杂度O(n),将树中序遍历的值存起来,看看是否是严格升序

class Solution(object):
    def isValidBST(self, root):
        """
        :type root: TreeNode
        :rtype: bool
        """
        rec = []
        self._in_order(root, rec)
        for i in range(1, len(rec)):
            if rec[i - 1] >= rec[i]:
                return False
        return True

    def _in_order(self, node, rec):
        if not node:
            return 
        if node.left:
            self._in_order(node.left, rec)
        rec.append(node.val)
        if node.right:
            self._in_order(node.right, rec)

450. 删除二叉搜索树中的节点

https://leetcode-cn.com/problems/delete-node-in-a-bst/

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。一般来说,删除节点可分为两个步骤:首先找到需要删除的节点;如果找到了,删除它。说明: 要求算法时间复杂度为 O(h),h 为树的高度。

示例:root = [5,3,6,2,4,null,7],key = 3

    5
   / \
  3   6
 / \   \
2   4   7

给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。

    5
   / \
  4   6
 /     \
2       7

另一个正确答案是 [5,2,6,null,4,null,7]。

    5
   / \
  2   6
   \   \
    4   7

题解

一:如果要删除的元素没有左子树,那么用该元素的右节点替换掉该元素即可;

        如果要删除的元素没有右子树,那么用该元素的左节点替换掉该元素即可;

       如果要删除的元素左右子树都存在,那么可以拿右子树的最左边的节点(后继)代替删除元素或者拿左子树的最右边的节点(左子树中的最大值)代替删除元素。

class Solution(object):
    def deleteNode(self, root, key):
        """
        :type root: TreeNode
        :type key: int
        :rtype: TreeNode
        """
        if not root:
            return root
        return self._deleteNode(root, key)
        
    # 返回删除节点后的根
    def _deleteNode(self, node, key):
        if not node:
            return node
        if node.val < key:
            # return self._deleteNode(node.right, key)
            node.right = self._deleteNode(node.right, key)
            # 注意将跟节点返回回去
            return node
        elif node.val > key:
            # return self._deleteNode(node.left, key)
            node.left = self._deleteNode(node.left, key)
            # 注意将跟节点返回回去
            return node
        else:
            # node
            # 没有左子树
            if not node.left:
                return node.right
            # 没有右子树
            if not node.right:
                return node.left
            # 既有左孩子又有右孩子
            if node.right and node.left:
                new_root_node = self._successor(node.right)
                # 删除其后继
                new_root_node.right = self._deleteNode(node.right, new_root_node.val)
                new_root_node.left = node.left
                return new_root_node

    def _successor(self, node):
        while node.left:
            node = node.left
        return node       

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

https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/

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

示例:

给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

题解

一:DFS深度遍历,构造一棵树的过程可以拆分成无数个这样的子问题:构造树的每个节点以及节点之间的关系。对于每个节点来说,都需要:选取根节点(因为要平衡,选取数组的中间值作为根),构造该节点的左子树,构造该节点的右子树

  1. 选择中间元素构建根节点node
  2. 构建左子树的根节点(左半部分的中间元素)
  3. 构建右子树的中间节点(右半部分的中间元素)
  4. 返回根节点
class Solution(object):
    def sortedArrayToBST(self, nums):
        """
        :type nums: List[int]
        :rtype: TreeNode
        """
        if not nums:
            return None
        return self._root(nums, 0, len(nums) - 1)

    def _root(self, nums, l, r):
        if l > r or l < 0 or r >= len(nums):
            return None
        mid = l + (r - l) // 2
        node = TreeNode(nums[mid])
        node.left = self._root(nums, l, mid - 1)
        node.right = self._root(nums, mid + 1, r)
        return node

230. 二叉搜索树中第K小的元素

https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。说明:你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

示例 1:输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 1
示例 2:输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 3
进阶:
如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest 函数?

题解

一:借用中序遍历的思想,将中序遍历的结果存入rec,则rec是升序的,返回rec[k-1]即可。

class Solution(object):
    def kthSmallest(self, root, k):
        """
        :type root: TreeNode
        :type k: int
        :rtype: int
        """
        rec = []
        self._helper(root, rec)
        return rec[k-1]
        
    def _helper(self, node, rec):
        if node:
            if node.left:
                self._helper(node.left, rec)
            rec.append(node.val)
            if node.right:
                self._helper(node.right, rec)

236. 二叉树的最近公共祖先

https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1,输出: 3,解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4,输出: 5,解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:所有节点的值都是唯一的。p、q 为不同节点且均存在于给定的二叉树中。

题解

一:先考察p和q是否是最近公共祖先,再考虑公共祖先不是p和q的情况,这个时候p和q必定分别位于左子树和右子树。

class Solution(object):
    def lowestCommonAncestor(self, root, p, q):
        """
        :type root: TreeNode
        :type p: TreeNode
        :type q: TreeNode
        :rtype: TreeNode
        """
        res = [root]
        if self._exist(p, q):
            return p
        if self._exist(q, p):
            return q
        self._recursive(root, p, q, res)
        return res[0]

    def _recursive(self, node, p, q, res):
        if not node:
            return False
        if p.val != node.val and q.val != node.val:
            if (self._exist(node.left, p) and self._exist(node.right, q)) \
            or (self._exist(node.left, q) and self._exist(node.right, p)):
                res[0] = node
                return True
        return self._recursive(node.left, p, q, res) or self._recursive(node.right, p, q, res)

    def _exist(self, root, node):
        if not root:
            return False
        if root.val == node.val:
            return True
        return self._exist(root.left, node) or self._exist(root.right, node)

二:转自官方题解,https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/er-cha-shu-de-zui-jin-gong-gong-zu-xian-by-leetcod/

这种方法非常直观。先深度遍历该树。当你遇到节点 p 或 q 时,返回一些布尔值作为标记。标记有助于确定是否在任何路径中找到了所需的节点。最不常见的祖先将是两个子树递归都返回 true 标记的节点。它也可以是一个节点,它本身是 p 或 q 中的一个,对于这个节点,子树递归返回一个 true 标记。

让我们看看基于这个想法的正式算法。

算法:

  1. 从根节点开始遍历树。
  2. 如果当前节点本身是 p 或 q 中的一个,我们会将变量 mid 标记为 true,并继续搜索左右分支中的另一个节点。
  3. 如果左分支或右分支中的任何一个返回 true,则表示在下面找到了两个节点中的一个。
  4. 如果在遍历的任何点上,left、right 或者 mid 三个标记中的任意两个变为 true,这意味着我们找到了节点 p 和 q 的最近公共祖先。

让我们看一个示例,搜索树中两个节点 9 和 11 的最近公共祖先。

1 --> 2 --> 4 --> 8
回溯 8 --> 4
4 --> 9(找到一个节点,返回 true )
回溯 9 --> 4 --> 2
2 --> 5 --> 10
回溯 10 --> 5
5 --> 11(找到另一个节点,返回 true )
回溯 --> 5 --> 2

在节点 2 这里出现 left = true 且 right = true,因此节点 2 是它们的最近公共祖先。

时间复杂度:O(N) ,N 是二叉树中的节点数,最坏情况下,我们需要访问二叉树的所有节点。
空间复杂度:O(N) ,这是因为递归栈使用的最大空间位 N ,斜二叉树的高度可以是 N 。

class Solution(object):
    def __init__(self):
        self.ans = None

    def lowestCommonAncestor(self, root, p, q):
        def recurse_tree(node):
            if not node:
                return False
            left = recurse_tree(node.left)
            right = recurse_tree(node.right)
            mid = (node.val == p.val or node.val == q.val)
            if mid + left + right >= 2:
                self.ans = node
            return mid or left or right
        
        recurse_tree(root)
        return self.ans
发布了46 篇原创文章 · 获赞 1 · 访问量 5029

猜你喜欢

转载自blog.csdn.net/qq_xuanshuang/article/details/105513298