Leetcode树专题

树的常识

三种遍历方法

  1. 深度遍历包括前中后序遍历三种;
  2. 广度优先遍历就是层次遍历。
    PS:
    前中后序遍历,如果使用递归遍历,都很简单易理解;
    如果使用非递归方式,首先想到的就应该是使用栈结构来控制整个过程,因为递归也是利用栈来实现的;
    前中后序遍历的非递归方式中,后序遍历的非递归方式相比较而言,略复杂。

四种主要的遍历思想为:
前序遍历:根结点 —> 左子树 —> 右子树
中序遍历:左子树—> 根结点 —> 右子树
后序遍历:左子树 —> 右子树 —> 根结点
层次遍历:只需按层次遍历即可

对应的数据结构

深度优先遍历:栈
广度优先遍历:队列

##阿里面试题:如何确保广度优先遍历的时候一层已经遍历完了?
https://www.cnblogs.com/simplepaul/p/6721687.html
可以参见文中技巧1和技巧2

Leetcode257.Binary Tree Paths

Given a binary tree, return all root-to-leaf paths.

For example, given the following binary tree:

1
/ \
2 3
\
5
All root-to-leaf paths are:

[“1->2->5”, “1->3”]
Credits:
Special thanks to @jianchao.li.fighter for adding this problem and creating all test cases.

思路:很典型的深度优先搜索配合栈,宽度优先搜索配合队列。学一个遍历二叉树的方法

# dfs + stack
def binaryTreePaths1(self, root):
    if not root:
        return []
    res, stack = [], [(root, "")]
    while stack:
        node, ls = stack.pop() #node=root, ls=" "
        if not node.left and not node.right:
            res.append(ls+str(node.val))
        if node.right:
            stack.append((node.right, ls+str(node.val)+"->"))
        if node.left:
            stack.append((node.left, ls+str(node.val)+"->"))
    return res

# bfs + queue
def binaryTreePaths2(self, root):
    if not root:
        return []
    res, queue = [], collections.deque([(root, "")])
    while queue:
        node, ls = queue.popleft()
        if not node.left and not node.right:
            res.append(ls+str(node.val))
        if node.left:#这句话如果和下一句 if node.right分支调换顺序结果也是一样的
            queue.append((node.left, ls+str(node.val)+"->"))
        if node.right:
            queue.append((node.right, ls+str(node.val)+"->"))
    return res

# dfs recursively
def binaryTreePaths(self, root):
    if not root:
        return []
    res = []
    self.dfs(root, "", res)
    return res

def dfs(self, root, ls, res):
    if not root.left and not root.right:
        res.append(ls+str(root.val))
    if root.left:
        self.dfs(root.left, ls+str(root.val)+"->", res)
    if root.right:
        self.dfs(root.right, ls+str(root.val)+"->", res)

Leetcode111. Minimum Depth of Binary Tree(求二叉树的最短深度)

学会这道题的套路以后就会做很多事了,比如下面这道题:

Given a binary tree, find its minimum depth.

The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.

72ms打败89.59%的python3答案

较差的思路:从上题思路套用而来,先求得所有路径的节点数(就是长度),再求最小值

# 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):
        """
        :type root: TreeNode
        :rtype: int
        """

        if not root:  
            return 0  
        res, stack = [], [(root,1)]  
        while stack:  
            node, length = stack.pop() #node=root  
            if not node.left and not node.right:  
                res.append(length)  
            if node.right:  
                stack.append((node.right, length+1))  
            if node.left:  
                stack.append((node.left, length+1))  
        return min(res)  

较好的思路:宽度优先搜索那就是用广度优先搜索!

套用第一题的套路得到如下运行了69ms打败了94.57%python3答案的solution:

# 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):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root:
            return 0
        res, queue = [], collections.deque([(root, 1)])
        while queue:
            node, ls = queue.popleft()
            if not node.left and not node.right:
                return ls #这里是重点,这里返回相当于返回了最小值,避免了不必要的遍历操作
            if node.left:
                queue.append((node.left, ls+1))
            if node.right:
                queue.append((node.right, ls+1))

Leetcode104.剑指OFFER面试题55 Maximum Depth of Binary Tree(二叉树的深度)

Given a binary tree, find its maximum depth.

The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.

69ms 81.30%/73.91%的python3用户的solution:

较差的思路1:深度优先搜索,以tuple形式存储每个节点的深度

# 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):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root:  
            return 0  
        res, stack = [], [(root,1)]  
        while stack:  
            node, length = stack.pop() #node=root  
            if not node.left and not node.right:  
                res.append(length)  
            if node.right:  
                stack.append((node.right, length+1))  
            if node.left:  
                stack.append((node.left, length+1))  
        return max(res)  

较差的思路2

看到一个更好的做法,65ms,这样不用把(节点,当前节点对应的深度(就是路径上的节点数))以tuple的形势存储。
这种做法我理解为宽度优先搜索

# 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):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root:
            return 0
        count = 0
        queue = [root]
        while queue:
            count += 1
            next_lvl = []
            for node in queue:
                if node.left:
                    next_lvl.append(node.left)
                if node.right:
                    next_lvl.append(node.right)
            queue = next_lvl
        return count

较好的思路(剑指OFFER思路):递归

跑了55ms,和python3 45ms的解法同思路

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

class Solution(object):
    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root:
            return 0

        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))

Leetcode662. Maximum Width Of Binary Tree(阿里面试题,广度优先搜索)

Given a binary tree, write a function to get the maximum width of the given tree. The width of a tree is the maximum width among all levels. The binary tree has the same structure as a full binary tree, but some nodes are null.

The width of one level is defined as the length between the end-nodes (the leftmost and right most non-null nodes in the level, where the null nodes between the end-nodes are also counted into the length calculation.

Example 1:
Input:

       1
     /   \
    3     2
   / \     \  
  5   3     9 

Output: 4
Explanation: The maximum width existing in the third level with the length 4 (5,3,null,9).
Example 2:
Input:

      1
     /  
    3    
   / \       
  5   3     

Output: 2
Explanation: The maximum width existing in the third level with the length 2 (5,3).
Example 3:
Input:

      1
     / \
    3   2 
   /        
  5      

Output: 2
Explanation: The maximum width existing in the second level with the length 2 (3,2).
Example 4:
Input:

      1
     / \
    3   2
   /     \  
  5       9 
 /         \
6           7

Output: 8
Explanation:The maximum width existing in the fourth level with the length 8 (6,null,null,null,null,null,null,7).
Note: Answer will in the range of 32-bit signed integer.

较好的思路

The main idea in this question is to give each node a position value. If we go down the left neighbor, then position -> position * 2; and if we go down the right neighbor, then position -> position * 2 + 1. This makes it so that when we look at the position values L and R of two nodes with the same depth, the width will be R - L + 1.

反思面试:
面试官描述题目的时候并没有提到空节点怎么算(不像这道题目描述的这么严谨,他可能就是在等我问他),面试官问我听明白了吗?我说听明白了。我想当然地以为是完全二叉树,没有考虑过如果一层里有空节点的话,那么该层的宽度怎么算。

我先说出了用广度优先遍历,遍历完每层记录宽度,再对宽度求最大值的思路。

面试官问我如何确保一层遍历完了?
我回答维护一个队列,在把左右孩子添加进去之前求该队列的长度,就是当前层的宽度。

其实我觉得我回答的没错,但是面试官显然还是不满意,他说希望能在空间还是时间上进行优化来着?忘了记不清了

现在想想他是不是在提示我用tuple的形式把每个节点和它所在的层次一块存储

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

class Solution(object):
    def widthOfBinaryTree(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        queue = [(root, 0, 0)]
        cur_depth = left = ans = 0
        for node, depth, pos in queue:
            if node:
                queue.append((node.left, depth+1, pos*2))
                queue.append((node.right, depth+1, pos*2 + 1))
                if cur_depth != depth:
                    cur_depth = depth
                    left = pos
                ans = max(pos - left + 1, ans)

        return ans

根据104题的这第二种做法,我们又可以完成 求每层的平均值,如下题:

Leetcode637. Average of Levels in Binary Tree

Given a non-empty binary tree, return the average value of the nodes on each level in the form of an array.
Example 1:

Input:
3
/ \
9 20
/ \
15 7
Output: [3, 14.5, 11]
Explanation:
The average value of nodes on level 0 is 3, on level 1 is 14.5, and on level 2 is 11. Hence return [3, 14.5, 11].
Note:

The range of node’s value is in the range of 32-bit signed integer.

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

class Solution:
    def averageOfLevels(self, root):
        """
        :type root: TreeNode
        :rtype: List[float]
        """
        if not root:  
            return 0   
        queue = [root]  
        res=[root.val]
        while queue:  
            next_lvl_node = []
            next_lvl_total=0#记录每层值的和
            for node in queue:  
                if node.left:  
                    next_lvl_node.append(node.left)
                    next_lvl_total=next_lvl_total+node.left.val
                if node.right:  
                    next_lvl_node.append(node.right)
                    next_lvl_total=next_lvl_total+node.right.val
            if len(next_lvl_node)!=0:#如果下一层有元素存在,注意这里必须用个数判断,不能用current_lvl_total!=0去判断
                res.append(next_lvl_total/len(next_lvl_node))
            queue=next_lvl_node    
        return res

这个答案119ms.

Leetcode112.Path Sum

(按照257的深度优先搜索方法改编的解法)

Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.

For example:
Given the below binary tree and sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.

打败了大概35%的用户

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

class Solution(object):
    def hasPathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: bool
        """
        if not root:  
            return False  
        res, stack = [], [(root,0)]  #存基准值
        while stack:  
            node, ls = stack.pop() #node=root, ls=" "  
            if not node.left and not node.right:  
                res.append(ls+node.val)  
            if node.right:  
                stack.append((node.right, ls+node.val))  
            if node.left:  
                stack.append((node.left, ls+node.val))

        return sum in res

改进一点,有符合的路径的时候立即停止运行程序:

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

class Solution(object):
    def hasPathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: bool
        """
        if not root:  
            return False  
        res, stack = [], [(root,0)]  #存基准值
        while stack:  
            node, ls = stack.pop() #node=root, ls=" "  
            if not node.left and not node.right:  
                if(ls+node.val )==sum:
                    return True
            if node.right:  
                stack.append((node.right, ls+node.val))  
            if node.left:  
                stack.append((node.left, ls+node.val))

        return False

62ms,打败了40.97%,和最快的48s解法的思想是一样的

Leetcode113.Path Sum II

思路:按照257的深度优先搜索方法改编的解法,题意:寻找路径和为给定值的所有路径

Given a binary tree and a sum, find all root-to-leaf paths where each path’s sum equals the given sum.

For example:
Given the below binary tree and sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
return

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

我的原创解法:

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

class Solution(object):
    def pathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: List[List[int]]
        """
        if not root:
            return []
        res, stack = [], [(root, [],0)]#初值,可以理解为占位用,代表类型
        while stack:
            node, ls,total = stack.pop() #node=root, ls=" "
            if not node.left and not node.right:
                res.append([ls+[node.val],total+node.val])
            if node.right:
                stack.append((node.right,ls+[node.val],total+node.val))
            if node.left:
                stack.append((node.left, ls+[node.val],total+node.val))

        return [result[0]  for result in res if result[1]==sum]

但是这个解法非常慢,125ms,打败了2.01%的python解法。
于是我看了看别人的答案发现可能是return的时候写了个for循环导致了没有必要的遍历,浪费了时间。于是改进成如下:69ms,打败了60%~70%+的python submission

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

class Solution(object):
    def pathSum(self, root, sum):
        """
        :type root: TreeNode
        :type sum: int
        :rtype: List[List[int]]
        """
        if not root:
            return []
        res, stack = [], [(root, [],0)]#初值,可以理解为占位用,代表类型
        while stack:
            node, ls,total = stack.pop() #node=root, ls=" "
            if not node.left and not node.right and (total+node.val)==sum:
                    res.append(ls+[node.val])
            if node.right:
                stack.append((node.right,ls+[node.val],total+node.val))
            if node.left:
                stack.append((node.left, ls+[node.val],total+node.val))

        return res

Leetcode404. Sum of Left Leaves

Find the sum of all left leaves in a given binary tree.

Example:

3
/ \
9 20
/ \
15 7

There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.

思路:分类讨论

按左child是叶子还是树讨论

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

class Solution(object):
    def sumOfLeftLeaves(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """

        if not root: return 0
        if root.left and not root.left.left and not root.left.right:#左子树不是树,是叶子
            return root.left.val + self.sumOfLeftLeaves(root.right)
        return self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)#左子树不是叶子,是树

Leetcode572. (剑指OFFER面试题26)Subtree of Another Tree (树的子结构)

Given two non-empty binary trees s and t, check whether tree t has exactly the same structure and node values with a subtree of s. A subtree of s is a tree consists of a node in s and all of this node’s descendants. The tree s could also be considered as a subtree of itself.

Example 1:
Given tree s:

3
/ \
4 5
/ \
1 2
Given tree t:
4
/ \
1 2
Return true, because t has the same structure and node values with a subtree of s.
Example 2:
Given tree s:

3
/ \
4 5
/ \
1 2
/
0
Given tree t:
4
/ \
1 2
Return false.

从Leetcode的举例来看,我们能明白这题和剑指OFFER上的题还不完全一样,isSubTree这个主函数是一样的,判断两棵树是否相同的函数DoesSHasT不一样。
剑指OFFER:S包含T即可,S的所有后代T不必完全都拥有,只要T这棵树在S这棵树中能找到即可。
LEETCODE:S包含T,S的所有后代T都必须完全拥有!
由于题意的不同,所以函数DoesSHasT也相应地改动了退出递归的判断条件。

剑指OFFER官方思路

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

class Solution(object):
    def isSubtree(self, s, t):
        """
        判断t是不是s的子树(s的所有孩子t都得一模一样)
        :type s: TreeNode
        :type t: TreeNode
        :rtype: bool
        """
        def DoesSHasT(s,t):
            """
            判断两棵树的结构是否完全一样
            """
            if t==None and s!=None:#t遍历完了,而S没遍历完,说明s的结构比t大,所以二者不完全一样
                    return False 
            if t==None and s==None:#t和s都遍历完了,说明二者一样
                    return True
            if t!=None and s==None:#t没遍历完,s遍历完了,说明s的结构比t小,所以二者不完全一样
                    return False
            #注意以上这些判断条件和剑指OFFER不同,因为需求改了
            if s.val!=t.val:
                return False

            return DoesSHasT(s.left,t.left)and DoesSHasT(s.right,t.right)


        result=False
        if(s!=None and t!=None):#其实本题已经说了是两棵非空树了,为了保持与剑指Offer一致还是做一下鲁棒性处理
                if s.val==t.val:
                #如果两棵树根节点相等,再往下判断有没有相同子结构;如果根节点的值都不相等,则result仍为False,往下进行
                    result=DoesSHasT(s,t)
                if result==False:
                    result=self.isSubtree(s.left,t)
                if result==False:#注意每一次都要重新判断result的布尔值
                    result=self.isSubtree(s.right,t)
        return result








我认为更好写的思路:通过中左右这样的中序遍历把t树和s树转换为字符串,判断t字符串是否在s字符串里(借鉴自该题某discussion)

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

class Solution(object):
    def isSubtree(self, s, t):
        """
        :type s: TreeNode
        :type t: TreeNode
        :rtype: bool
        """

       def convert(p):
            return "^" + str(p.val)  + convert(p.left) + convert(p.right) if p else "$"

        return convert(t) in convert(s)

理解“^”在这里的目的,比如s是单节点树12,t是单节点树2,那么如果不加“^”,则会返回True,因为2在12里,但实际t并不在s里;所以加上“^”以后防止了这种情况,因为^2不在^12里,符合t不在s里的事实。
这种做法跑了115ms,打败了89%的用户。

猜你喜欢

转载自blog.csdn.net/u011462357/article/details/79389115