题目描述
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
分析
先考虑只需要返回一个层序遍历结果而不用考虑到底是第几层。如上面只返回[3,9,20,15,7]
我们可以借助于一个队列Q:
首先把根元素放到Q中,while Q不为空时,一个个取出队首,将其不为null的子节点加入队列,直到队列为空。
用上面的例子,先把第一层3加入队列,然后3出队,加入子节点9,20,这是第二次;然后分别取出9,20,加入子节点15,7,这是第三次,取出15,7,他们没有子节点,没有元素入队,队为空,结束。
这个实现是比较简单的:
//层序 : 利用队列
public void layerTraversal(TreeNode root) {
Queue<TreeNode> q= new LinkedBlockingQueue<>();
TreeNode tmp ;
q.add(root);
while (q.size()>0){
tmp = q.poll();
System.out.print(tmp.val+" ");
if (tmp.left!=null)q.add(tmp.left);
if (tmp.right!=null)q.add(tmp.right);
}
}
实际上也可以利用数组,只不过稍微复杂点,原理一样。
//层序 : 利用数组
public void layerTraversal_iter_arr(TreeNode root){
TreeNode []treeNodes= new TreeNode[100];//缺点:难以预测大小,要么浪费空间,要么不足报错
int in=0,out=0;
treeNodes[in++]=root;
while (in>out){
if (treeNodes[out]!=null){
System.out.print(treeNodes[out].val+" ");
treeNodes[in++]=treeNodes[out].left;
treeNodes[in++]=treeNodes[out].right;
}
out++;
}
}
现在加需求了:需要考虑层次,即需要把不同层的分开。
我们如何在原来的基础上进行扩展了。
考虑前面使用队列的实现,一个队列存放了所有的元素,不能区分层次。如果使用两个队列存放,那么不同层不就可以交替使用,从而分开了吗?
比如有两个队列Q1、Q2,按上面的例子:
- 把root 3放到Q1
- Q1中3出队变空,子节点9,20放到Q2
- Q2出队变空,9,20的子节点15,7放到Q1
- Q1元素全部出队,没有子节点,两个队列都空了,程序结束。
那么现在就好实现了:
辅助双队列
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new LinkedList<>();//结构
LinkedList<TreeNode> q1 = new LinkedList<>();//Q1
LinkedList<TreeNode> q2 = new LinkedList<>();//Q2
LinkedList<TreeNode> cur,empty;//引用,分别指向当前使用的队列和空队列
TreeNode tmp;
if (root!=null)q1.add(root); //先把root加入Q1
while (!q1.isEmpty() || !q2.isEmpty()){//至少一个队列不为空
List<Integer> level = new LinkedList<>();
if (q1.isEmpty()){ //使cur指向正在使用的队列,empty指向即将用于存储的空队列
cur = q2;
empty = q1;
}else{
cur = q1;
empty= q2;
}
while (!cur.isEmpty()){//当前队列不为空,取出所有元素,把元素的非null子节点放到empty队列
tmp = cur.pop();
level.add(tmp.val);
if (tmp.left!=null)empty.add(tmp.left);
if (tmp.right!=null)empty.add(tmp.right);
}
res.add(level);
}
return res;
}
性能:
执行用时 :1 ms, 在所有 Java 提交中击败了91.30%的用户
内存消耗 :39.8 MB, 在所有 Java 提交中击败了5.71%的用户
空间开销有点大,可以考虑使用数组代替队列。
看了下其他答案,上面的解法还可以优化。
我使用的是两个队列供不同层次交替使用从而分层,但是还有一种方式————添加分界节点。
辅助单队列+分界节点
在上面的实现基础上,层之间添加一个分界节点。
使用上面的例子说明下:
使用X表示分界节点
- 3入队,X入队
- 3,X出队,子节点9,20入队,X入队
- 9,20,X出队,子节点15,7入队,X入队
- 15,7,X出队,没有元素入队,X也不用入队,代表程序结束 (即:X只有在队列不为空的情况下入队)
实现:
public List<List<Integer>> levelOrder(TreeNode root) {
LinkedList<TreeNode> q = new LinkedList<>();//辅助队列
TreeNode dummy = new TreeNode(Integer.MAX_VALUE);//分界节点
List<List<Integer>> res = new LinkedList<>();//result
List<Integer> level =new LinkedList<>();//层元素数组
TreeNode tmp;//临时节点
if (root==null)return res;
q.add(root);
q.add(dummy);//第一层和第二层之间的分界节点
while (!q.isEmpty()){
tmp = q.pop();
if (tmp==dummy){//遇到分界节点,表示已经遍历完一层了
res.add(level);
level = new LinkedList<>();//下一层的存放数组
if(!q.isEmpty()) q.add(dummy);//dummy只有在队列不为空的情况下入队,保证程序正常退出
}else{//非分界节点
level.add(tmp.val);
if (tmp.left!=null)q.add(tmp.left);
if (tmp.right!=null)q.add(tmp.right);
}
}
return res;
}
性能其实和上面差不多,空间其实也没省多少:元素都会放到队列中,双队列多一个队列的空间,单队列多一个分界节点。
两种思维而已。
递归实现
前面两种层序遍历都是迭代实现,其实也可以递归实现
递归实现的话由于不是一层一层的遍历的,那么怎么控制层数???
我们可以添加一个参数level,代表当前的层数,这样就可以把元素添加到对应的层中的list中了,不管你是怎么递归,只要把元素遍历完就好了。
实现:
private List<List<Integer>> res = new LinkedList<>();//result
public List<List<Integer>> levelOrder_recur(TreeNode root){
if (root==null)return res;
helper(root,0);
return res;
}
private void helper(TreeNode node,int level){
if (res.size()==level)res.add(new LinkedList<>());//添加某层的List
res.get(level).add(node.val);//获取level层,并添加元素
if (node.left!=null)helper(node.left,level+1);//递归下去
if (node.right!=null)helper(node.right,level+1);
}
递归实现代码量最少,也很容易理解。
总结
总共写了三种方法,层序和递归,如果要归一个类的话,就是BFS和DFS
- BFS,双队列
- BFS,单队列+分界节点
- DFS,使用level参数记录层次
三种方法时间复杂度、空间复杂度都是O(N)