算法框架6 二叉树遍历及其应用

1. 中序遍历

类型1:对于一个二叉查找树来说,其中序遍历结果是一个有序数组

面试题 17.12. BiNode

def convertBiNode(self, root: TreeNode) -> TreeNode:
    self.ans = self.pre = TreeNode(-1)
    def dfs(root):
        if not root:
            return
        dfs(root.left)
        root.left = None
        self.pre.right = root
   # 当第一次执行到下面这一行代码,恰好是在最左下角, 这个时候 self.pre = root 就切断了 self.pre         
   # 和 self.ans 的联系
   # 之后 self.pre 的变化都不会体现到 self.ans 上。
   # 直观上来说就是 self.ans 在遍历到最左下角的时候下车了
   # 因此最后返回 self.ans 即可
        self.pre = root
        dfs(root.right)
    dfs(root)
    return self.ans.right

说明:https://leetcode-cn.com/problems/binode-lcci/solution/di-gui-dfs-tu-jie-mian-shi-ti-1712-binode-by-fe-lu/

self.pre 和 self.ans 这2个变量, 指向的都是同一个内存中的对象, 也就是 Treenode(-1) 这个对象: self.pre = self.ans = TreeNode(-1)

因为他们指向的是同一个对象, 而且这个Treenode对象属于 "可变类型对象" : https://blog.csdn.net/find_a/article/details/82384639

所以,对可变类型对象进行操作时, id不会改变,也就是说,还是在相同的内存空间中,对于Treenode(-1)进行修改 (修改它的left 和 right属性).

所以,在递归的过程中,一直递归到第一次到达叶子节点之后, 作者对当前的叶子节点(因为是二叉搜索树的中序遍历,所以这个叶子节点就是整颗树最左边的那个节点)做了以下操作:

root.left = None
self.pre.right = root
self.pre = root

其中第一句 root.left 只是设置当前节点的左节点, 不重要. 第二句 self.pre.right = root. 这句代码作用是将当前节点设置“前置节点”,方便下一个节点用它,也不重要。 重要的在于第三句 self.pre = root, 这句代码的关键之处在于, 他将 self.pre 这个变量,直接指向了另一块内存空间, 也就是 "root" 节点所在的内存空间。这一指,就把self.pre 和 self.ans 断开的联系。什么意思呢?当执行完这一句命令后,目前:

sell.pre 指向的,是内存中 root 节点所在的内存。

self.ans 指向的,还是之前内存中 Treenode(-1) 哑节点的位置。

也就是说,在执行 self.pre = root 这句程序之前, self.pre 做的任何改变, 都会对 Treenode(-1) 生效,同时,由于self.ans 也指向同一个Treenode(-1) 数据,所以等于也会对self.ans 生效。 但是,当执行完这句命令后, self.pre 之后做出任何改变,都不再改变那个我们在整个算法最开头创建出来的哑节点 Treenode(-1) 了。但是,我们看到上面第二句做了一个很关键的操作,就是 self.pre.right = root , 这一句在第三局之前,就将哑节点的right属性,设置成了“整颗树最左边的叶子节点,也就是我们需要最终返回的有序链表的头节点”。重要!!!!记住,这里self.pre 和 self.ans 仍然指向的是同一个内存地址中的哑节点Treenode(-1), 也就是说,目前self.ans.right 已经是整棵树最左边的叶子节点了。 然后,执行完第三句之后,self.pre = root 这句命令让Self.pre 不再指向Treenode(-1), 所以让self.ans永远停留在当前状态。也就是“self.ans.right=整棵树最左下角的叶子节点”的状态。这个状态,我们就可以直接在递归结束后返回self.ans.right.

2. 后序遍历

剑指 Offer 55 - II. 平衡二叉树

示例 1:

给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

       1
      / \
     2   2
    / \
   3   3
  / \
 4   4
返回 false 。

策略:后序遍历+ 剪枝

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        def recur(root):
            if not root:
                return 0
            left = recur(root.left)
            if left == -1:
                return -1
            right = recur(root.right)
            if right == -1:
                return -1
            #后序遍历
            return max(left,right)+1 if abs(left-right)<=1 else -1
        return recur(root) != -1

综合:

剑指 Offer 07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        def recur(root,left,right):
            #相等就是自己
            if left > right:
                return
            #root是在先序里面的
            node = TreeNode(preorder[root])
            # 有了先序的,再根据先序的,在中序中获 当前根的索引
            idx = dic[preorder[root]]
            #左子树的根节点就是左子树的(前序遍历)第一个,就是+1,左边边界就是left,右边边界是中 
            #间区分的idx-1
            node.left = recur(root+1, left, idx-1)

            #由根节点在中序遍历的idx 区分成2段,idx 就是根

            #右子树的根,就是右子树(前序遍历)的第一个,就是当前根节点 加上左子树的数量
            # root 当前的根  左子树的长度 = 左子树的左边-右边 (idx-1 - left+1) 。 
            # 最后+1就是右子树的根了
            node.right = recur(root+idx-left+1,idx+1,right)
            return node

        dic = {}
        for i in range(len(inorder)):
            dic[inorder[i]] = i
        return recur(0,0,len(inorder)-1)

递推参数: 根节点在前序遍历的索引 root 、子树在中序遍历的左边界 left 、子树在中序遍历的右边界 right ;

终止条件: 当 left > right ,代表已经越过叶节点,此时返回 nullnull ;

递推工作:

建立根节点 node : 节点值为 preorder[root] ;
划分左右子树: 查找根节点在中序遍历 inorder 中的索引 idx;这样建立起了inorder和preorder的关系

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页