二叉树面试

  • python实现二叉树的遍历以及基本操作

104. 二叉树的最大深度

111. 二叉树的最小深度

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

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

102. 二叉树的层次遍历

103. 二叉树的锯齿形层次遍历 ***

543. 二叉树的直径 ***

100. 相同的树

101. 对称二叉树

617. 合并二叉树

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

109. 有序链表转换二叉搜索树 ***

110. 平衡二叉树 ***

226. 翻转二叉树

404.左叶子之和

257.二叉树的所有路径

112. 路径总和

437.路径总和 III ***

124.二叉树中的最大路径 ***

297. 二叉树的序列化与反序列化

98.验证二叉搜索树

572.另一个树的子树

1367.二叉树中的列表

199. 二叉树的右视图

二叉树的遍历

104. 二叉树的最大深度

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def maxDepth(self, root: TreeNode) -> int: 
        '''
        时间:O(n), 因为每个节点都需要被访问一次
        空间复杂度:递归需要使用调用栈
                - 最坏: 树完全不平衡,每个节点都只有右节点此时需要递归n次, O(n)
                - 最好: 树完全平衡即树额高度是log(n), O(log(n))
        '''
        if not root:
            return 0
        return 1 + max(self.maxDepth(root.left),  self.maxDepth(root.right))

111. 二叉树的最小深度

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def minDepth(self, root: TreeNode) -> int:
        '''
        和求最大深度不同: 
        最小深度 还需要单独考虑当root 没有左子树或右子树  的两种情况
        (因为这时直接套用递归公式1 + min(self.minDepth(root.left), self.minDepth(root.right))) 
         会导致结果总为 1+0,不是正确的最小深度
        eg 树为[1,2]时: minDepth应该为2而不是1
        '''
        if not root:
            return 0
        elif not root.left:
            return 1+self.minDepth(root.right)
        elif not root.right:
            return 1+self.minDepth(root.left)
        else:
            return 1 + min(self.minDepth(root.left), self.minDepth(root.right))

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

说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        #这个解法所有节点只会遍历一次。
        if not root or root.val==p.val or root.val==q.val: #如果root为空(返回root=空) 或者p或q结点的值有一个和root的值相等那么最近公共祖先肯定是root
            return root
        
        left = self.lowestCommonAncestor(root.left, p, q) #在root的左子树中看是否可以找到p q的最近公共祖先LCA并返回LCA
        right = self.lowestCommonAncestor(root.right, p, q)

        if not left: #因为p、q 为不同节点且均存在于给定的二叉搜索树中。所以若在左子树中没有找到(p和q均不在左子树中),那么右子树中找到的结果肯定就是p q的LCA
            return right
        if not right: #同理 若在右子树中没有找到,那么左子树中找到的结果肯定就是p q的LCA
            return left
        
        return root #最后的一种情况就是:left和right均不为空,这样一来肯定左右子树都是通过条件root.val==p.val or root.val==q.va跳出递归的,
        #也就是说: 对于 lowestCommonAncestor 这个函数的理解的话,它不一定可以返回最近的共同祖先,
        #         如果子树中只能找到 p 节点或者 q 节点,它最终返回其实就是 p 节点或者 q 节点。
        #         这其实对应于最后一种情况,也就是 left 和 right 都不为 null,说明 p 节点和 q 节点分处于两个子树中,直接 return root

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

  • 二叉搜索树(二叉排序树)的中序遍历是 递增序列
# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 236的解法可直接用于该题,
        # 但因为该题是二叉搜索树(有序),所以通过和根节点的值进行比较就能确定p,q是在左子树还是右子树(p、q 为不同节点且均存在于给定的二叉搜索树中)
        if not root or root.val==p.val or root.val==q.val:
            return root
        if p.val < root.val  and  q.val < root.val: #p、q均在左子树
            return self.lowestCommonAncestor(root.left, p, q)
        if p.val > root.val  and  q.val > root.val: #p、q均在右子树
            return self.lowestCommonAncestor(root.right, p, q)
        return root #p、q一个在左子树一个在右子树

102. 二叉树的层次遍历

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        ### 广度优先遍历BFS: 需要用到辅助队列
        if not root:
            return []

        res=[]
        queue=[root] #记住辅助队列中存放TreeNode
        while queue:
            temp=[] #每次存放树的一层
            #保证每次while循环里queue里只有树中的一层元素
            for _ in range(len(queue)):
                r=queue.pop(0)
                temp.append(r.val)#在弹出的时候加入该层临时temp中
                if r.left: #不为空的时候加入队列
                    queue.append(r.left)
                if r.right:
                    queue.append(r.right)

            res.append(temp)#将该层加入到最终结果res中

        return res


        ### DFS递归实现 
        if not root:
            return []
        res = []
        def dfs(index,r):#每次递归的时候都需要带一个index(表示当前的层数),如果当前行对应的list不存在,就加入一个空list进去。
            # 假设res是[ [1],[2,3] ], index是3,就再插入一个空list放到res中
            if len(res)

103. 二叉树的锯齿形层次遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        
        queue=[root]
        res=[]
        index=1 #树的行标号
        while queue:
            temp=[]

            for _ in range(len(queue)):
                r = queue.pop(0)
                temp.append(r.val)
                if r.left:
                    queue.append(r.left)
                if r.right:
                    queue.append(r.right)
            
            if index%2==0: #在102层次遍历中只是改变: 偶数行需要翻转后再加入res中
                temp.reverse() 
                #list.reverse()仅是list的一个内置翻转的方法,直接改变了原有的list, 返回值是一个None,其作用的结果需要通过打印被作用的列表才可以查看出具体的效果(在字典,字符串或者元组中,是没有这个内置方法的) 
                #reversed(list)是python自带的一个方法,准确说是一个类,返回反向迭代器,在经过reversed()的作用之后,返回的是一个把序列值经过反转之后的迭代器,所以需要通过遍历,或者List,或者next()等方法,获取作用后的值 ( list(reversed(List))  , tuple(reversed(Tuple), ''.join(reversed(str)))
            
            res.append(temp)
            index+=1

        return res

543. 二叉树的直径

错误解答: 最大直径是左子树和右子树的最大深度之和,但是万一最大直径没有经过根节点呢?所以说对于树中的每一个结点,都要把它视为根节点,然后比较所有结点的左子树和右子树的最大深度之和,取其中的最大值。

image

  • 二叉树上的任一“路径”上一定有一个结点是所有其他结点的祖先结点(因为“路径”是由一个个父子关系连接而成的),即对于任一结点,以此结点为根的diameter就可以表示为左子树高度 + 右子树高度,而二叉树的diameter就是所有结点为根的diameter中最大的那个
# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution: #时间复杂度:O(n),  空间复杂度:O(n)
    def __init__(self):
        self.maxDiameter=0 #全局变量 , 树中所有结点(作为”路径根结点“)的最大直径即为树的直径
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        
        def traceDepth(r): #辅助函数, 在计算每个r子树最大深度的同时计算其”直径“并更新最大”直径“ , 比104题只多了一步”更新操作“
            if not r:
                return 0            
            leftDepth=traceDepth(r.left) #这样先用变量保存防止后面两步中的重复计算
            rightDepth=traceDepth(r.right)
            
            self.maxDiameter=max(self.maxDiameter, leftDepth+rightDepth) ###更新最大直径, leftDepth + rightDepth = 以 r 为”路径根结点“时的直径(左子树最大深度 + 右子树最大深度)
            
            return 1 + max(leftDepth, rightDepth)  # 求最大深度的递归公式
        
        traceDepth(root) #从root开始遍历整个树这样每个节点都会遍历到, 过程中self.maxDiameter会一直更新为最大的值
        return self.maxDiameter

100. 相同的树

  • 如果两棵树都是空树,返回True,
  • 如果两棵树都不是空,那么分别判断两棵树的根节点的数值,左结点和右结点是否相同
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        if not p and not q: 
            return True
        if p and q:
            return p.val==q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
        return False #剩下左或右子树只有一个为空的情况均是不相等的二叉树

101. 对称二叉树

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        ### 递归方法:
        '''
        时间复杂度:O(n),因为遍历了整个输入树一次,所以总的运行时间为 O(n),其中n是树中结点的总数。
        空间复杂度:递归调用的次数受树的高度限制。在最糟糕情况下,树是线性的,其高度为 O(n), 此时由栈上的递归调用造成的空间复杂度为 O(n) 
        '''
        if not root:
            return True
        
        def isTwoSymmetric(p, q): #判断两个子树是否对称
            if not p and not q:
                return True
            if p and q:
                return p.val==q.val and isTwoSymmetric(p.left, q.right) and isTwoSymmetric(p.right, q.left)
            return False

        return isTwoSymmetric(root.left, root.right)



        ### 迭代方法:
        if not root:
            return True
        queue=[root.left, root.right] 
        while queue:
            l=queue.pop(0)
            r=queue.pop(0) #每次从队列中取出两个节点进行比较

            if not(l or r): #德摩根定律:not l and not r, 即两个节点都为空时,必须是continue因为队列里可能剩有其他偶数个节点
                continue
            if not(l and r) or l.val != r.val: #not(l and r)必须在前,这是说明两个节点只有一个为空时就肯定不对称,后面l.val != r.val也就不用判断而且也不能判断(空节点没有val)
                return False

            queue.extend([l.left, r.right, l.right, r.left]) #按照镜像对称的方式加入下一波子节点
        return True
对称二叉树迭代(队列)解法示意图

617. 合并二叉树

class Solution:
    def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:
        '''
同步遍历,对于相同位置上的一对结点n1和n2,处理策略是:
若n1和n2都存在,则只需要保留其中一个结点(如n1),将另一结点的值加到此结点上即可(如n1.val += n2.val)。
若n1或n2任一不存在,则合并后的二叉树对应位置上的结点就是存在的那个了。
若n1和n2都不存在,则合并后仍不存在

时间复杂度:O(N),其中 NN 是两棵树中节点个数的较小值。
空间复杂度:O(N),在最坏情况下,会递归 N 层,需要 O(N) 的栈空间。
        '''
        if t1 and t2: #都存在的话每次保留t1的节点
            t1.val += t2.val
            t1.left = self.mergeTrees(t1.left, t2.left)
            t1.right = self.mergeTrees(t1.right, t2.right)
            return t1
        return t1 if t1 else t2

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

二叉搜索树(Binary Search Tree)是指一棵空树或具有如下性质的二叉树:
  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值
  • 任意节点的左、右子树也分别为二叉搜索树
  • 没有键值相等的节点
    基于以上性质,我们可以得出一个二叉搜索树的特性:二叉搜索树的中序遍历结果为递增序列。
如何构造树

构造一棵树的过程可以拆分成无数个这样的子问题:构造树的每个节点以及节点之间的关系。对于每个节点来说,都需要:

  1. 选取节点
  2. 构造该节点的左子树
  3. 构造该节点的右子树
    因题目要求构造一棵「高度平衡」的树,所以我们在选取节点时选择数组的中点作为根节点,以此来保证平衡性。
  • 中序+后序、中序+先序才可以唯一确定一棵二叉树,先序+后序遍历不能唯一确定
  • 高度平衡: 意味着每次必须选择升序数组中的中间数字作为根节点。这对于奇数个数的数组选择唯一;但对于偶数个数的数组,要么选择中间位置左边的元素作为根节点,要么选择中间位置右边的元素作为根节点,不同的选择方案会创建不同的平衡二叉搜索树。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode: #最后还要返回创建后的树也就是root节点
        # #每次选择升序数组的中间元素作为根结点来构造二叉树
        # #时间: O(n),  空间: O(N),二叉搜索树空间O(N),递归栈深度O(logN)
        if not nums:
            return None

        mid_index = len(nums) // 2 #得到数组中的”中间元素“的index
        root = TreeNode(nums[mid_index]) #选择数组中的中间元素值构造根结点

        root.left = self.sortedArrayToBST( nums[:mid_index] ) #递归构造左子树
        root.right = self.sortedArrayToBST( nums[mid_index+1:] )

        return root#返回根结点(也就是数组的中间元素作为的根结点)

109. 有序链表转换二叉搜索树

与108一样的要求,只是把升序数组换成了升序链表

# Definition for singly-linked list.
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    ### 利用 快慢指针 找到链表的中间node再类似108题一样来构造平衡树
    # 时间:O(NlogN) 
    # 空间:O(logN)。使用递归需要考虑递归栈的空间复杂度。对于一棵非平衡二叉树O(N),
    # 但问题描述中要求维护一棵平衡二叉树,所以保证树的高度上界为O(logN),因此空间复杂度为O(logN)。
    def sortedListToBST(self, head: ListNode) -> TreeNode:
        '''
        slow_ptr 每次向后移动一个节点而 fast_ptr 每次移动两个节点
        当 fast_ptr 到链表的末尾时 slow_ptr 就访问到链表的中间元素
        对于一个偶数长度的数组,中间两个元素都可用来作二叉搜索树的根
        '''
        def findmid(head, tail):
            slow = head
            fast = head
            while fast != tail and fast.next!= tail :
                slow = slow.next
                fast = fast.next.next
            return slow
        
        def helper(head, tail): #递归利用链表构造树
            if  head == tail: 
                return  None
            node = findmid(head, tail)
            root = TreeNode(node.val)
            root.left = helper(head, node)
            root.right = helper(node.next, tail)
            return root
            
        return helper(head, None) #只有尾结点的next为None




    ### 先将链表转换成一个升序数组后再进行108中的操作,拿空间换时间
    # 时间:O(N) 
    # 空间:O(N)。使用递归需要考虑递归栈的空间复杂度。对于一棵非平衡二叉树O(N),
    # 但问题描述中要求维护一棵平衡二叉树,所以保证树的高度上界为O(logN),因此空间复杂度为O(logN)。
    def sortedListToBST(self, head: ListNode) -> TreeNode:  
        def mapListToValues(head): #将链表转换成数组
            vals = []
            while head:
                vals.append(head.val)
                head = head.next
            return vals 
        
        nums=mapListToValues(head)
 
        def sortedArrayToBST(nums):  
            # #每次选择升序数组的中间元素作为根结点来构造二叉树
            # #时间: O(n),  空间: O(N),二叉搜索树空间O(N),递归栈深度O(logN)
            if not nums:
                return None
            mid_index = len(nums) // 2  
            root = TreeNode(nums[mid_index])  
            root.left = sortedArrayToBST( nums[:mid_index] )  
            root.right = sortedArrayToBST( nums[mid_index+1:] )
            return root#返回根结点 

        return sortedArrayToBST(nums)    

110. 平衡二叉树

# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        # 自顶向下的递归: 利用104题中判断二叉树最大深度的函数,左子树和右子树的深度差小于等于1即为平衡二叉树
        # 时间:O(NlogN), 空间:O(N),如果树完全倾斜,递归栈可能包含所有节点。
        def depth(root): #计算一个树的(最大)深度/高度
            if not root:
                return 0
            return 1 + max( depth(root.left), depth(root.right) )
        
        if not root:
            return True
        if abs(depth(root.left) - depth(root.right))>1: #一旦一个子树的左右树高度差绝对值大于1就肯定不是平衡的
            return False
        return self.isBalanced(root.left) and self.isBalanced(root.right) #继续递归判断下一波左右子节点






    def isBalanced(self, root: TreeNode) -> bool:
        # 自底向上的递归: 上面的方法计算depth存在大量冗余。
        # 自底向上计算,每个子树的高度只会计算一次。可以递归先计算当前节点的子节点高度,然后再通过子节点高度判断当前节点是否平衡,从而消除冗余。
        # 使用与方法一中定义的depth()方法。自底向上与自顶向下的逻辑相反,首先判断子树是否平衡,然后比较子树高度判断父节点是否平衡
        # 时间:O(N),计算每棵子树的高度和判断平衡操作都在恒定时间内完成。 空间:O(N)
 
        def helper(root): # 计算root树高度的同时判断子树的高度差是否大于1,一旦发现大于1就提前阻断
            if not root:
                return 0
            
            left = helper(root.left) 
            right = helper(root.right) 
            if left==-1 or right==-1: # 判断左右子树是否有一个不平衡
                return -1 #一旦进入这个分支就表示该树不平衡
            
            return max(left, right)+1 if abs(right - left)<=1 else -1 
            #计算深度或直接返回不平衡的结果-1,这样一旦发现不平衡后函数就会一直返回-1不用再计算深度达到提前阻断的效果(树的高度不可能是-1,所以若helper返回-1就表示已经不平衡了)
         
        return helper(root) != -1 #helper(root)返回-1(不平衡)时整个函数就返回 False
        


        ### ”这个方法并没有提前阻断“
        # self.res = True  # 这样在子函数中不用声明nonlocal就可以改变res的值
        # def helper(root): # 计算root树高度的同时判断子树的高度差是否大于1,一旦发现大于1就提前阻断
        #     if not root:
        #         return 0
            
        #     left = helper(root.left) 
        #     right = helper(root.right) 
        #     if abs(right - left) > 1: # 判断:一旦进入这个分支就表示该树不平衡
        #         self.res = False
            
        #     return max(left, right) + 1 # 计算深度
        # helper(root)
        # return self.res

226. 翻转二叉树

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        ### 递归: 直接从字面上,当root非空时利用python交换左右节点即可
        # 时间:O(N) 
        # 空间:O(N)        
        if root:
            root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
        return root


        ### 迭代: 利用DFS,stack
        if not root:
            return None        
        stack=[root] #或者使用队列queue, 后面pop()改成pop(0)就好了
        while stack:
            node=stack.pop()
            node.left, node.right = node.right, node.left

            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return root

404.左叶子之和

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def sumOfLeftLeaves(self, root: TreeNode) -> int:
        left_sum=0 #会累加
        if not root:
            return 0
        if root.left and not root.left.left and not root.left.right: #围绕root.left, 保证其一定是左叶子节点
            left_sum+=root.left.val
        return left_sum + self.sumOfLeftLeaves(root.left)+self.sumOfLeftLeaves(root.right)

257.二叉树的所有路径

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        ### 迭代DFS来实现: 时间空间复杂度均为O(N)
        if not root:
            return []

        paths=[]
        stack=[(root, str(root.val))] #栈内存放(节点, 到该点为止的路径)
        while stack:
            node, path = stack.pop()
            if not node.left and not node.right: #到达叶子节点的时候, 就可以将该条路径加入到paths中
                paths.append(path)
            if node.right:
                stack.append( (node.right,   path + '->' + str(node.right.val)) )
            if node.left:
                stack.append( (node.left,    path + '->' + str(node.left.val)) )

        return paths



        ### 递归
        def construct_paths(root, path): #递归获取的路径的同时在paths中加入路径
            if root:
                path += str(root.val)
                if not root.left and not root.right:  # 当前节点是叶子节点
                    paths.append(path)  #把路径加入到paths中,外层函数中的string变量paths是可变类型不用再声明nonlocal就可以直接改变其值
                else: # 当前节点不是叶子节点,继续递归遍历
                    path += '->'   
                    construct_paths(root.left, path)
                    construct_paths(root.right, path)

        paths = []
        construct_paths(root, '')
        return paths

112. 路径总和

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        # 普通递归: 注意首先要明确 问题终止条件, 然后在分别考虑左右子树上同样的操作或判断
        '''
        时间:O(N), 访问每个节点一次
        空间:最坏情况下,整棵树是非平衡的,例如每个节点都只有一个孩子,递归会调用N次(树的高度,因此栈的空间开销是 O(N)
             最好情况下,树是完全平衡的,高度只有log(N),因此在这种情况下空间复杂度只有 O(log(N)) 
        '''
        if not root:
            return False
        if not root.left and not root.right and root.val==sum: #当前的root节点无左右节点且root的值与当前的sum相等,说明已到达叶子节点且已找到一条路径
            return True
        return self.hasPathSum(root.left, sum-root.val) or self.hasPathSum(root.right, sum-root.val) #继续在左或右子树中找和为sum-root.val的路径
        



        # 利用深度优先策略访问每个节点,同时更新剩余的目标和。
        # 用栈将递归转成迭代的形式。深度优先搜索在除了最坏情况下都比广度优先搜索更快。
        # 最坏情况是指满足目标和的 root->leaf 路径是最后被考虑的,这种情况下深度优先搜索和广度优先搜索代价是相通的
        '''
        时间:O(N), 访问每个节点一次
        空间:最坏情况下 O(N)
             最好情况下 O(log(N)) 
        '''
        if not root:
            return False

        stack = [(root, sum), ] #从包含根节点的栈开始模拟,开始目标为sum
        while stack:
            node, curr_sum = stack.pop()
            if not node.left and not node.right and node.val == curr_sum:  #与上面的递归类似的判断
                return True
            #继续DFS遍历并判断左右子树,非空则压入栈中留着接下来判断, 
            if node.right:
                stack.append((node.right, curr_sum - node.val)) 
            if node.left:
                stack.append((node.left, curr_sum - node.val)) #先进后出所以每次都是先判断左子树           
 
        return False #遍历完整个stack/树都没有进入True的那个分支就说明没有找到

437. 路径总和 III

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:

    def pathSum(self, root: TreeNode, sum: int) -> int:
        '''
        双重递归,效率非常低,存在大量重复计算
        '''
        if not root:
            return 0
        # 返回 从root点出发的路径个数pathSumFrom + root的左子树上所有的路径个数pathSum + root的右子树上所有的路径个数pathSum    
        return self.pathSumFrom(root, sum) + self.pathSum(root.left, sum) + self.pathSum(root.right, sum)

    def pathSumFrom(self, node, sum): #(从该点出发)以每个节点为根节点,算一遍路径和为sum的有几条
        if not node:
            return 0
        return (1 if node.val == sum else 0) + self.pathSumFrom(node.left, sum - node.val) + self.pathSumFrom(node.right, sum - node.val)        


    

    
    
    ### 时间:O(N), 注意题中:路径方向必须是向下的(必须从父节点到子节点)
    def pathSum(self, root: TreeNode, sum: int) -> int:
        '''
        优化: 类似560,利用前缀和,hashtable 进行优化
        '''
        prefixSumTree = {0:1} #key是前缀和, value是大小为key的前缀和出现的次数
        self.count = 0 #满足条件的路径计数, 加上self. 可以在内层函数dfs()中直接改变其值 不用再声明nonlocal
        
        prefixSum = 0 #从 根结点 到当前结点的前缀和(路径上的结点之和)
        self.dfs(root, sum, prefixSum, prefixSumTree)
        
        return self.count

    def dfs(self, root, sum, prefixSum, prefixSumTree):
        if not root:
            return 0
        prefixSum += root.val #加上当前结点值的前缀和
        
        #查找在 <前缀和, 该前缀和出现次数> 的hashmap上,有没有前缀和为 prefixSum-sum 的节点,
        #存在,即表示有路径之和为sum的路径存在,count加上key=prefixSum-sum的value。然后递归进入左右子树。
        oldSum = prefixSum - sum  
        if oldSum in prefixSumTree:# 看是否在哈希表中存在
            self.count += prefixSumTree[oldSum]
        
        # 将当前的前缀和存入哈希表中,不存在:初始化(0+1),存在:原来的次数+1
        prefixSumTree[prefixSum] = prefixSumTree.get(prefixSum, 0) + 1
        
        self.dfs(root.left, sum, prefixSum, prefixSumTree)
        self.dfs(root.right, sum, prefixSum, prefixSumTree)
        
        '''前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
        左右子树遍历完成之后,回到当前层,需要把当前节点添加的前缀和的一次 次数 去掉。避免回溯之后影响上一层。
        因为思想是前缀和,不属于前缀的,我们就要去掉它。'''
        prefixSumTree[prefixSum] -= 1 ### 千万不能忘

124. 二叉树中的最大路径

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        '''
        时间:O(N)
        空间: O(log(N))。我们需要一个大小与树的高度相等的栈开销,对于二叉树空间开销是O(log(N)
       
        - 先递归计算出”单边的最大值“(包含该节点,若 left, right 小于 0 则不包含节点下该分支)
        - 该节点下最大值是 left + right + val (若 left, right 小于 0 则不包含节点下该分支),比较所有节点下的最大值 所求即为 二叉树中的最大路径和
        '''
        def maxPathGain(node): 
            # 返回的是以node为 ”路径起始节点“(非中间连接节点) 的最大收益(只能沿着左或右走,要么极端情况就是不选左也不选右(为负时设为0)该路径只有node.val) 
            if not node: #无关给定的二叉树root非不非空,这只是递归函数的出口,几乎所有二叉树的递归函数都要有 ”not node“ 的判断
                return 0
            
            left_gain = max(maxPathGain(node.left), 0) #不为负:如果是负收益就 不选中 该左子树,即 left_gain设为0
            right_gain = max(maxPathGain(node.right), 0)

            self.max_sum = max(self.max_sum,  node.val + left_gain + right_gain) #更新max_sum,
            # node.val + left_gain + right_gain :其可以表示 node为中间连接节点或为起始节点(往左或往右或只有node.val)的多种情况,
            # 是因为前面left/right_gain计算时使用了max(x,0), 可表示选或不选  
            
            return node.val + max(left_gain, right_gain) #以node为起始点, 往最大收益的那边走(单边最大值)
            

        self.max_sum = float('-inf') #因为结点可能为负值,所以最大值要初始化为负无穷
        maxPathGain(root)
        return self.max_sum

297. 二叉树的序列化与反序列化

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        :type root: TreeNode
        :rtype: str
        """
        ### 前序遍历 实现二叉树序列化和反序列化
        if not root:
            return 'null'
        return str(root.val) + ',' + self.serialize(root.left) + ',' + self.serialize(root.right)
        
    def deserialize(self, data):
        """Decodes your encoded data to tree.
        :type data: str
        :rtype: TreeNode
        """
        def helper(arr):
            if not arr:
                return None
            root_val = arr.pop(0) #得到根结点的val, 同时arr中还要去掉根val, 因为后面还要递归遍历
            if root_val == 'null':
                return None
            else:
                root = TreeNode(root_val)
                root.left = helper(arr)
                root.right = helper(arr)
            return root
        return helper(data.split(','))
            

    def serialize(self, root):
        """Encodes a tree to a single string    
        :type root: TreeNode
        :rtype: str
        """
        ### 层序遍历 实现二叉树序列化和反序列化
        if not root:
            return ''
        queue = [root]
        s = ''
        while queue:
            node = queue.pop(0)
            if not node:
                s += 'null,'
                continue
            s += str(node.val) + ','
            queue.append(node.left)
            queue.append(node.right)
        return s

    def deserialize(self, s):
        """Decodes your encoded data to tree.
        :type data: str
        :rtype: TreeNode
        """
        if not s:
            return None
        ls = s.split(',')[:-1] #最后一个结点后面的逗号要去掉
        def helper(idx):
            if idx >= len(ls):
                return None
            root = TreeNode(ls[idx])
            root.left = helper(2*idx+1)
            root.right = helper(2*idx+2)
            return root
        return helper(0)
        

# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))

98. 验证二叉搜索树

572.另一个树的子树

先判断当前的两棵树是否相等,再判断t是不是s左子树的子树,或者右子树的子树

1367.二叉树中的列表

199. 二叉树的右视图

你可能感兴趣的:(二叉树面试)