和二叉树相伴的美好时光~@labuladong Day2 - 递归

文章目录

  • 写在前面
  • 二叉树思考学习记录
    • Day2 拆分问题的递归思维
    • Day2 [练习](https://alidocs.dingtalk.com/document/edit?dentryKey=dvlkVKjKXFkDqqWy#%20%E3%80%8C%E7%AC%AC%E4%BA%8C%E5%A4%A9%E4%BD%9C%E4%B8%9A%E3%80%8D)
      • 1.1 [前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal)
      • 1.2 [中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/)
      • 1.3 [后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal)
      • 2.1 [二叉树最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree)
      • 2.2 [二叉树最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree)
      • 2.3 [路径和](https://leetcode-cn.com/problems/path-sum)
      • 2.4 [二叉树展开为链表](https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list)
      • 3.1 [二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths)
      • 3.2 [左叶子之和](https://leetcode-cn.com/problems/sum-of-left-leaves)
      • 4 [零钱兑换](https://leetcode-cn.com/problems/coin-change/)
    • Day2 的思考与补充

写在前面

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

二叉树思考学习记录

Day2 拆分问题的递归思维

今天是递归本归~重要的事情念 3 遍:

  • 结构相同,规模更小;
  • 结构相同,规模更小;
  • 结构相同,规模更小。

之所以说今天是递归本归,是因为重点想强调对问题的拆分,化整为零、分治、通过小问题求解大问题的思想,而不是“自己调用自己”的表象。Day1 的内容中也有很多递归函数,但是却更强调通过遍历树来求解问题。

Day2 练习

不用 traverse 辅助函数完成前中后序遍历:

1.1 前序遍历

    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 本函数接收一棵树的根节点(指针),返回该树的前序遍历
        self.res = []
        if not root: return self.res
        self.res.append(root.val)
        self.res += self.preorderTraversal(root.left)
        self.res += self.preorderTraversal(root.right)
        return self.res

1.2 中序遍历

    def inorderTraversal(self, root: TreeNode) -> List[int]:
        # 本函数输入为一棵树的根节点(指针),返回该树的中序遍历
        if not root: return []
        res = []
        res += self.inorderTraversal(root.left)
        res.append(root.val)
        res += self.inorderTraversal(root.right)
        return res

1.3 后序遍历

    def postorderTraversal(self, root: TreeNode) -> List[int]:
        # 本函数输入为一棵树的根节点(指针),返回该树的后序遍历
        if not root: return []
        res = []
        res += self.postorderTraversal(root.left)
        res += self.postorderTraversal(root.right)
        res.append(root.val)
        return res

使用「大问题分解成子问题」的思维模式完成以下题目:

2.1 二叉树最大深度

本函数输入为一棵树的根节点(指针),返回该树的最大深度。

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

2.2 二叉树最小深度

本函数输入为一棵树的根节点(指针),返回该树的最小深度。

    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

2.3 路径和

    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)

2.4 二叉树展开为链表

一开始忘记给左子树赋值为 None 了。
但是:是不是这样递归写就不太满足题目要求了Do not return anything,虽然返回的确实是最初树的根节点 root,但是确实通过了 return。如果不写 return,left = self.flatten(root.left) 的操作就完不成,后面也就不能 root.right = left 穿针引线了。

    def flatten(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        # 本函数接受一棵树的根节点(指针)为输入,返回该树对应的“链表”的根节点(指针)
        if not root: return root
        left = self.flatten(root.left)
        right = self.flatten(root.right)
        root.right = left
        root.left = None
        pt = root
        while pt.right:
            pt = pt.right
        pt.right = right
        return root

又想了以下,补了一个无 return 版本~

    def flatten(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        if root:
            self.flatten(root.left)
            self.flatten(root.right)
            root.right = root.left
            root.left = None
            
            while root.right:
                root = root.right
            root.right = root.right

复习昨天遍历的思路完成以下题目:

3.1 二叉树的所有路径

因为就想严格套用回溯模板,所以 dfs 得到路径数字 list 再转 str。这里稍微折腾了一下,就是节点的左孩子和右孩子是否在“选择”列表中,应该是孩子节点不为空的时候才进行 dfs。

    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        res = []
        def dfs(track, root):
            if not root: return
            track.append(root.val)
            if not root.left and not root.right:
                res.append(track[:])
                return
            else:
                for child in [root.left, root.right]:
                    if not child: continue
                    dfs(track, child)
                    track.pop()
                return
        dfs([], root)
        return ['->'.join([str(n) for n in x]) for x in res]

            

额,上面 append 和 pop 写的不对称,我是不是其实不该用回溯啊,用回溯就意味着得先“放弃” root,补了一版单独判断 root + 回溯的。

    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        res = []
        def dfs(track, root):
            if not root: return
            if not root.left and not root.right:
                res.append(track[:])
                return
            for child in [root.left, root.right]:
                if not child: continue
                track.append(child.val)
                dfs(track, child)
                track.pop()
            return
        if not root: return []
        
        dfs([root.val], root)
        return ['->'.join([str(n) for n in x]) for x in res]

所以还是补一个遍历好了,这样也不用单独写 root 了。

    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        self.res = []
        self.path = []

        def helper(root):
            if root is None: return
            self.path.append(root.val)
            if not root.left and not root.right:
                self.res.append(self.path[:])
            helper(root.left)
            helper(root.right)
            self.path.pop()
            return
        
        helper(root)
        return ['->'.join([str(n) for n in x]) for x in self.res]

3.2 左叶子之和

遍历思路:

    def isLeafNode(self, root):
        return not root.left and not root.right

    def sumOfLeftLeaves(self, root: TreeNode) -> int:
        # 通过遍历树来求解
        self.res = 0

        def helper(root):
            if root is None:
                return
            if root.left:
                if self.isLeafNode(root.left):
                    self.res += root.left.val
                else:
                    helper(root.left)
            helper(root.right)
            return
        
        helper(root)
        return self.res

分治思路:

    def isLeafNode(self, root):
        return not root.left and not root.right

    def sumOfLeftLeaves(self, root: TreeNode) -> int:

        if not root: return 0
        res = 0
        if root.left:
            if self.isLeafNode(root.left):
                res += root.left.val
            else:
                res += self.sumOfLeftLeaves(root.left)
        

        res += self.sumOfLeftLeaves(root.right)
            
        return res
                

4 零钱兑换

暴力枚举,方法可行仅供练习,但过不了 leetcode,会超时。

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        if amount == 0: return 0
        # 维护两个变量记录深度和最小深度
        self.min_num = amount + 1
        self.cnt = 0

        def dfs(amount):
            if amount < min(coins): return
            
            for coin in coins:
                # 稍微剪枝
                if coin > amount: continue
                # 选择了该 coin
                self.cnt += 1
                # 如果正好凑够,更新最小深度
                if amount - coin == 0:
                    self.min_num = min(self.min_num, self.cnt)
                dfs(amount - coin)
                # 撤销选择
                self.cnt -= 1
            return

        dfs(amount)
        return -1 if self.min_num == amount + 1 else self.min_num

Day2 的思考与补充

  • 规模更小,结构相同;
  • 相信递归函数本身的定义,而不要跳进递归。“自己调用自己”,过去和未来的自己你都不需要过问,只做好现在的自己该做的事就可以。

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