和二叉树相伴的美好时光~@labuladong Day1 - 遍历

文章目录

  • 写在前面
  • 二叉树思考学习记录
    • Day1 有关二叉树的前序中序后序遍历
    • Day1 [练习](https://alidocs.dingtalk.com/document/preview?dd_user_keyboard=false&dt_editor_toolbar=true&biz_ver=10&dentryKey=pL1N8BgleFJzOOPQ&type=d&dd_close=false)
      • 1. [前序+中序构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)
      • 2. [中序 + 后序 构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/)
      • 3.1 [求二叉树最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)
      • 3.2 [求 N 叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/)
      • 3.3 [二叉树最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/)
      • 3.4 [路径总和](https://leetcode-cn.com/problems/path-sum/submissions/)
      • 3.5 [路径总和2](https://leetcode-cn.com/problems/path-sum-ii/)
      • 3.6 [翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/)
      • 4. 打印二进制数
      • 5. [全排列](https://leetcode-cn.com/problems/permutations/)
    • Day1 的思考及补充

写在前面

本篇全部集中在二叉树相关问题上,是参考东哥的思路进行的练习和思考。东哥有《labuladong 的算法小抄》以及宝藏微信公众号 labuladong,github 也有项目,自来水推荐购买和关注。

二叉树思考学习记录

Day1 有关二叉树的前序中序后序遍历

重要的事情念 3 遍,重在遍历遍历遍历~

二叉树的遍历本身就仅仅是递归而已,无论是否有前序中序后序,这个递归都在那里,做它自己的事情。前序中序后序遍历只是在递归过程不同的时间点做操作而已,如下图。
和二叉树相伴的美好时光~@labuladong Day1 - 遍历_第1张图片

如果你想要通过遍历二叉树来寻求某些问题的解,需要想明白两个问题:

  • 你要对当前节点做什么操作?
  • 你要在什么时刻做这个操作?

二叉树相关的算法可以分为两大类:

  • 通过遍历二叉树解决,就要想好上面的两个问题。比如回溯就是在遍历一棵树,在进入节点前做选择(前序遍历的位置),在离开节点后撤销选择(后续遍历的位置)。
  • 通过将二叉树拆分成更小规模的问题来解决。比如归并或动态规划的思想。

有关上面两大类算法的一个典型题目是求一棵二叉树的深度。

  • 算法 1:维护两个全局变量,一个是最终的结果 res,一个是遍历二叉树过程中当前层的深度 depth。depth 应在前序遍历位置 +1,然后再从后序遍历位置 -1。而 res 每次都和 depth 取较大值即可。
  • 算法 2:将问题拆分成 max(左子树深度, 右子树深度)+ 1,递归下去解决。

Day1 练习

1. 前序+中序构造二叉树

一棵二叉树的前序遍历的结果为 [3,9,20,15,7] ,中序遍历的结果为 [9,3,15,20,7] ,请你画出该二叉树。
和二叉树相伴的美好时光~@labuladong Day1 - 遍历_第2张图片

2. 中序 + 后序 构造二叉树

一棵二叉树的后序遍历的结果为[9,15,20,7,3] ,中序遍历的结果为[9,3,15,20,7] ,请你画出该二叉树。

和二叉树相伴的美好时光~@labuladong Day1 - 遍历_第3张图片

3.1 求二叉树最大深度

思路一:通过遍历二叉树的方式,在不同时刻做操作来求取二叉树深度。

    def maxDepth(self, root: TreeNode) -> int:
        self.depth = 0
        self.res = 0
        def helper(root):
            if not root:
                return
            self.depth += 1
            self.res = max(self.res, self.depth)
            helper(root.left)
            helper(root.right)
            self.depth -= 1
            return
        
        helper(root)
        return self.res

思路二:通过分治的思想,将问题规模缩小为求左子树和右子树的深度。

    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

3.2 求 N 叉树的最大深度

思路一:与上问题如出一辙,通过遍历树来求取。

    def maxDepth(self, root: 'Node') -> int:
        self.depth = 0
        self.res = 0

        def helper(root):
            if root is None:
                return
            self.depth += 1

            self.res = max(self.res, self.depth)
            for child in root.children:
                helper(child)
            self.depth -= 1
            return

        helper(root)
        return self.res

思路二:通过分治求取子树深度继而求取整棵树的深度。

    def maxDepth(self, root: 'Node') -> int:
        if not root:
            return 0
        return max([0] + [self.maxDepth(child) for child in root.children]) + 1

3.3 二叉树最小深度

思路一:通过遍历二叉树

    def minDepth(self, root: TreeNode) -> int:
        # 与最大深度如出一辙
        self.res = float('Inf')
        self.depth = 0

        def helper(root):
            if not root:
                return
            self.depth += 1
            # 当且仅当叶子节点时更新 res
            if not root.left and not root.right:
                self.res = min(self.res, self.depth)
            helper(root.left)
            helper(root.right)
            self.depth -= 1

            return
        if not root:
            return 0
        helper(root)
        return self.res

思路二:分治

    def minDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        # 比最大深度问题多了一点需要额外考虑的,就是左子树为空或者右子树为空的时候
        if not root.left:
            return self.minDepth(root.right) + 1
        elif not root.right:
            return self.minDepth(root.left) + 1

        return min(self.minDepth(root.left), self.minDepth(root.right)) + 1

3.4 路径总和

版本一:维护一个全局变量,来表示是否存在目标和,仅当叶子节点时做判断。缺点:未剪枝,必须全部遍历完。

    def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
        self.sum = 0
        self.exist = False

        def helper(root):
            if not root:
                return 
            self.sum += root.val
            if not root.left and not root.right and self.sum == targetSum:
                self.exist = True

            helper(root.left)
            helper(root.right)
            self.sum -= root.val
            return
        
        helper(root)
        return self.exist

思路二: 递归分治

    def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
        # 本函数输入为一棵树的根节点(指针),返回该树是否有和为 targetSum 的路径
        if not root: return False
        if not root.left and not root.right and targetSum == root.val: return True
        return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)

3.5 路径总和2

	def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]:
	        # 维护变量
	        self.sum = 0
	        self.res = []
	        track = []
	
	        def helper(root, track):
	            if not root:
	                return 
	            # 进入节点,更新变量
	            track.append(root.val)
	            self.sum += root.val
	
	            # 判断叶子节点以及路径和
	            if not root.left and not root.right and self.sum == targetSum:
	                self.res.append(track[:])
	
	            helper(root.left, track)
	            helper(root.right, track)
	            
	            # 撤销更新
	            track.pop()
	            self.sum -= root.val
	
	            return
	        
	        helper(root, track)
	        return self.res

3.6 翻转二叉树

    def invertTree(self, root: TreeNode) -> TreeNode:

        def helper(root):
            if not root:
                return root
            temp = root.right
            root.right = helper(root.left)
            root.left = helper(temp)
            return root

        return helper(root)

4. 打印二进制数

和二叉树相伴的美好时光~@labuladong Day1 - 遍历_第4张图片
打印十进制只需要把上述代码 for 循环中的 2 改为 10 即可,但是此时打印高位会为 0,比如数字 9 会作为 09 打印。

5. 全排列

python 里面 in 方法在数据规模很大的时候会很耗时,但这里暂定认为 nums 大小比较小,所以直接用 in 方法。

    def permute(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        track = []
        self.res = []

        def dfs(track):
            if len(track) == n:
                self.res.append(track[:])
                return
            
            for i in range(n):
                if nums[i] in track:
                    continue
                track.append(nums[i])
                dfs(track)
                track.pop()
            return

        dfs(track)
        return self.res

Day1 的思考及补充

  • 二叉树问题里面遍历方式“做选择和撤销选择” 都在 for 循环外面,比如 N 叉树深度问题;但是回溯算法比如全排列或者生成 10 进制数,“做选择和撤销选择”都在 for 循环里面。因为这两种情况不一样?比如二叉树深度问题 depth 确实是对当前节点做的,而不是它的子节点;而回溯全排列 track 是对下一个位置填空,所以要对它的“子节点”(选择列表)进行“做选择和撤销选择”?

  • 上述这条应该这么理解,回溯模板写在 for 循环里面,就“抛弃”了 root,但其实 root 在回溯这里是无用的,root 下面的分支才是你可以做的“选择”。

  • 因为集中用递归的方式,所以迭代都略过。

你可能感兴趣的:(算法,二叉树)