二叉树的层次遍历
图论中的深搜和广搜可以应用在不同的数据结构中,二叉树的递归遍历是深搜,层次遍历是广搜
解法一:借助队列实现
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Deque<TreeNode> que = new ArrayDeque<>();
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(root != null) que.offerFirst(root);
while(!que.isEmpty()){
int size = que.size();
List<Integer> tmp = new ArrayList<>();
while( size -- > 0){
TreeNode cur = que.pollLast();
tmp.add(cur.val);
if(cur.left!=null) que.offerFirst(cur.left);
if(cur.right!=null) que.offerFirst(cur.right);
}
res.add(tmp);
}
return res;
}
}
解法二:利用二叉树的前序遍历,DFS的思路。依赖前序遍历自顶向下、自左向右的顺序,是从左到右的「列序遍历」。
class Solution {
List<List<Integer>> res = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
traverse(root,0);
return res;
}
public void traverse(TreeNode root, int depth){
if(root == null) return ;
// 前序位置,看看是否已经存储 depth 层的节点了
if(res.size() <= depth){
// 第一次进入 depth 层
res.add(new ArrayList<>());
}
// 前序位置,在 depth 层添加 root 节点的值
res.get(depth).add(root.val);
traverse(root.left,depth+1);
traverse(root.right,depth+1);
}
}
解法三:递归解法,自顶向下的「层序遍历」。把二叉树的每一层抽象理解成单链表的一个节点进行遍历
class Solution {
List<List<Integer>> res = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) return res;
List<TreeNode> curLevelNodes = new ArrayList<>();
curLevelNodes.add(root);
traverse(curLevelNodes);
return res;
}
public void traverse(List<TreeNode> curLevelNodes){
if(curLevelNodes.isEmpty()) return ;
// 前序位置,计算当前层的值和下一层的节点列表
List<TreeNode> nextLevelNodes = new ArrayList<>();
List<Integer> tmp = new ArrayList<>();
for(TreeNode cur : curLevelNodes){
tmp.add(cur.val);
if(cur.left!=null) nextLevelNodes.add(cur.left);
if(cur.right!=null) nextLevelNodes.add(cur.right);
}
// 前序位置添加结果,可以得到自顶向下的层序遍历
res.add(tmp);
traverse(nextLevelNodes);
// 后序位置添加结果,可以得到自底向上的层序遍历结果
}
}
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
104.二叉树的最大深度
111.二叉树的最小深度
226.翻转二叉树
- 只需要交换每个节点的左右孩子
- 遍历顺序为:前序或后序。中序遍历会把某些节点的左右孩子翻转了两次(可以避免,容易踩坑)
解法一:递归解法,遍历的思想
class Solution {
/**
* 前后序遍历都可以
* 中序不行,因为先左孩子交换孩子,再根交换孩子(做完后,右孩子已经变成了原来的左孩子),再右孩子交换孩子(此时其实是对原来的左孩子做交换)
*/
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
解法二:迭代解法,前后序遍历
//前序遍历
class Solution {
public TreeNode invertTree(TreeNode root) {
Deque<TreeNode> st = new ArrayDeque<>();
if(root != null) st.offerFirst(root);
while(!st.isEmpty()){
TreeNode cur = st.pollFirst();
TreeNode tmp =cur.left;
cur.left = cur.right;
cur.right = tmp;
if(cur.right!=null) st.offerFirst(cur.right);
if(cur.left!=null) st.offerFirst(cur.left);
}
return root;
}
}
//后序遍历
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
Deque<TreeNode> st = new ArrayDeque<>();
TreeNode cur = root;
TreeNode prev = null;
while(cur != null || !st.isEmpty()){
if(cur != null){
//遍历到最左节点
st.offerFirst(cur);
cur = cur.left;
}else{
cur = st.pollFirst();
//cur.right == null :当前节点为子树根节点
//cur.right == prev : 右子树是上一次处理的节点则处理根节点
if(cur.right == null || cur.right == prev){
TreeNode tmp =cur.left;
cur.left = cur.right;
cur.right = tmp;
prev = cur;
//这一步很重要,置空保证左子树不会被二次遍历。因为处理当前节点后,要向上走了
cur = null;
}else{
st.offerFirst(cur);
cur = cur.right;
}
}
}
return root;
}
}
//统一迭代,前序遍历
class Solution {
public TreeNode invertTree(TreeNode root) {
Stack<TreeNode> st = new Stack<>();
if(root != null) st.push(root);
while(!st.isEmpty()){
TreeNode cur = st.pop();
if(cur != null){
st.push(cur);
st.push(null);
if(cur.right!=null) st.push(cur.right);
if(cur.left!=null) st.push(cur.left);
}else{
cur = st.pop();
TreeNode tmp = cur.left;
cur.left = cur.right;
cur.right = tmp;
}
}
return root;
}
}
解法三:层次遍历
class Solution {
public TreeNode invertTree(TreeNode root) {
Deque<TreeNode> que = new ArrayDeque<>();
if(root != null) que.offerFirst(root);
while(!que.isEmpty()){
TreeNode cur = que.pollLast();
TreeNode tmp = cur.left;
cur.left = cur.right;
cur.right = tmp;
if(cur.left!=null) que.offerFirst(cur.left);
if(cur.right!=null) que.offerFirst(cur.right);
}
return root;
}
}
101. 对称二叉树
- 判断根节点的左子树和右子树是否可以沿着中轴线相互翻转
- 对称二叉树要比较的不是左右孩子,而是两个子树的外侧、内侧是否相等
递归解法
- 遍历顺序:后序,因为上一个节点需要用到子树的信息
- 一个树的遍历顺序是左右中,一个树的遍历顺序是右左中
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root== null) return true;
return compare(root.left,root.right);
}
public boolean compare(TreeNode left, TreeNode right){
if(left == null && right != null) return false;
else if(left != null && right == null) return false;
else if(left == null && right == null) return true;
else if(left.val != right.val) return false;
boolean outside = compare(left.left,right.right);
boolean inside = compare(left.right,right.left);
return outside && inside;
}
}
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root== null) return true;
return compare(root.left,root.right);
}
public boolean compare(TreeNode l, TreeNode r){
if(l == null && r == null) return true;
else if( l == null || r == null || l.val != r.val) return false;
return compare(l.left,r.right) && compare(l.right,r.left);
}
}
迭代解法
- 把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root== null) return true;
Deque<TreeNode> que = new LinkedList<>();
que.offerFirst(root.left);
que.offerFirst(root.right);
while(!que.isEmpty()){
TreeNode leftNode = que.pollLast();
TreeNode rightNode = que.pollLast();
if(leftNode == null && rightNode == null) continue;
if(leftNode == null || rightNode == null || leftNode.val != rightNode.val) return false;
que.offerFirst(leftNode.left);
que.offerFirst(rightNode.right);
que.offerFirst(leftNode.right);
que.offerFirst(rightNode.left);
}
return true;
}
}