- 树的常识
- Leetcode257.Binary Tree Paths
- Leetcode111. Minimum Depth of Binary Tree(求二叉树的最短深度)
- Leetcode104.剑指OFFER面试题55 Maximum Depth of Binary Tree(二叉树的深度)
- Leetcode662. Maximum Width Of Binary Tree(阿里面试题,广度优先搜索)
- Leetcode637. Average of Levels in Binary Tree
- Leetcode112.Path Sum
- Leetcode113.Path Sum II
- Leetcode404. Sum of Left Leaves
- Leetcode572. (剑指OFFER面试题26)Subtree of Another Tree (树的子结构)
树的常识
三种遍历方法
- 深度遍历包括前中后序遍历三种;
- 广度优先遍历就是层次遍历。
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%的用户。