代码随想录算法训练营第十五天 | 层序遍历、226.翻转二叉树、101.对称二叉树
二叉树的层序遍历
层序遍历一个二叉树就是从左到右一层一层的去遍历每个节点。这种遍历的方式需要借用一个辅助数据结构即队列来实现,队列先进先出的特性符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。层序遍历方式就是图论中的广度优先遍历,只不过这里应用在二叉树上。
102. 二叉树的层序遍历
题目链接:102. 二叉树的层序遍历
思路:使用队列实现。首先将根节点加入队列,当队列不为空时,用一维数组记录每一层的各元素,并用 size 来控制弹出的元素个数(size 代表每一层的元素个数)。每弹出一个元素,就将它的左右孩子(不为空)加入队列。以下代码是解决类似问题的一个模板。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> que = new LinkedList<TreeNode>();
List<List<Integer>> result = new ArrayList<List<Integer>>();
if (root != null) que.offer(root);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<Integer>();
int size = que.size();
while (size-- > 0) {
TreeNode node = que.poll();
itemList.add(node.val);
if (node.left != null) que.offer(node.left);
if (node.right != null) que.offer(node.right);
}
result.add(itemList);
}
return result;
}
}
107. 二叉树的层序遍历 II
题目链接:107. 二叉树的层序遍历 II
思路:与上一题基本类似,只是最后将 result 数组反转后返回即可。
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
Queue<TreeNode> que = new LinkedList<TreeNode>();
if (root != null) que.offer(root);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<Integer>();
int size = que.size();
while (size-- > 0) {
TreeNode node = que.poll();
itemList.add(node.val);
if (node.left != null) que.offer(node.left);
if (node.right != null) que.offer(node.right);
}
result.add(itemList);
}
Collections.reverse(result);
return result;
}
}
199. 二叉树的右视图
题目链接:199. 二叉树的右视图
思路:层序遍历时,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回 result 就可以了。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
// 注意本题此处的循环不能用 while
// 因为要找到单层的最后元素并不意味着是 size == 1 的时候
// size 随着每个节点的遍历在不断变化
// while (size-- > 0) {
// TreeNode node = queue.poll();
// if (size == 1) result.add(node.val);
// if (node.left != null) queue.offer(node.left);
// if (node.right != null) queue.offer(node.right);
// }
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (i == (size - 1)) result.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return result;
}
}
637. 二叉树的层平均值
题目链接:637. 二叉树的层平均值
思路:在层序遍历的时候把每一层求个总和再取一个均值。
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<Double> result = new ArrayList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
itemList.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
double sum = 0;
for (int i : itemList) {
sum += i;
}
sum /= itemList.size();
result.add(sum);
}
return result;
}
}
429. N 叉树的层序遍历
题目链接:429. N 叉树的层序遍历
思路:依然是二叉树的层序遍历思想,只不过一个节点有多个孩子。
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
Queue<Node> queue = new LinkedList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
int size = queue.size();
while (size-- > 0) {
Node node = queue.poll();
itemList.add(node.val);
if (node.children != null) {
for (Node i : node.children) {
queue.offer(i);
}
}
}
result.add(itemList);
}
return result;
}
}
515. 在每个树行中找最大值
题目链接:515. 在每个树行中找最大值
思路:层序遍历时找到每一层的最大值即可。
class Solution {
public List<Integer> largestValues(TreeNode root) {
List<Integer> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
int max = Integer.MIN_VALUE;
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
max = node.val > max ? node.val : max;
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(max);
}
return result;
}
}
116. 填充每个节点的下一个右侧节点指针
题目链接:116. 填充每个节点的下一个右侧节点指针
思路:层序遍历时,若当前遍历的节点不是该层的最后一个节点,则将当前节点的 next 指针指向队列中的下一个元素。
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
Queue<Node> queue = new LinkedList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
Node node = queue.poll();
if (i != (size - 1)) node.next = queue.peek();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return root;
}
}
117.填充每个节点的下一个右侧节点指针II
题目链接:117.填充每个节点的下一个右侧节点指针II
思路:与上一题的区别在于,本题给的二叉树不是完美二叉树,但照搬上一题的代码即可,因为已对左右孩子是否为 null 做了判断。
class Solution {
public Node connect(Node root) {
Queue<Node> queue = new LinkedList<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
Node node = queue.poll();
if (i != (size - 1)) node.next = queue.peek();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return root;
}
}
104.二叉树的最大深度
题目链接:104.二叉树的最大深度
思路:本题与层序遍历的关系为——遍历的层数即为二叉树的最大深度。因此每遍历一层,计数器++,最后返回即可。
class Solution {
public int maxDepth(TreeNode root) {
// 剪枝
if (root == null) return 0;
int result = 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result++;
}
return result;
}
}
111.二叉树的最小深度
题目链接:111.二叉树的最小深度
思路:层序遍历时,若当前节点的左右孩子都为空的时候,说明遍历到最近的叶子节点了(如果只是其中一个孩子为空,则不是最低点),直接返回计数器的值即可。
class Solution {
public int minDepth(TreeNode root) {
// 剪枝
if (root == null) return 0;
int result = 1;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
if (node.left == null && node.right == null) {
return result;
} else {
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
result++;
}
return result;
}
}
226. 翻转二叉树
题目链接:226. 翻转二叉树
思路:首先要明确选择哪种遍历方式。对于本题,使用前序遍历、后序遍历和层序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转两次。
递归实现
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return root;
// 以下为前序遍历
swapNode(root); // 中
invertTree(root.left); // 左
invertTree(root.right); // 右
// 以下为后序遍历
// invertTree(root.left); // 左
// invertTree(root.right); // 右
// swapNode(root); // 中
// 以下为中序遍历(仍可实现,但易踩坑,须自己想明白)
// invertTree(root.left); // 左
// swapNode(root); // 中
// invertTree(root.left); // 右 (这里依然要遍历左孩子,因为中间节点已经翻转了)
return root;
}
public void swapNode(TreeNode node) {
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
}
层序遍历实现
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {return null;}
ArrayDeque<TreeNode> deque = new ArrayDeque<>();
deque.offer(root);
while (!deque.isEmpty()) {
int size = deque.size();
while (size-- > 0) {
TreeNode node = deque.poll();
swap(node);
if (node.left != null) deque.offer(node.left);
if (node.right != null) deque.offer(node.right);
}
}
return root;
}
public void swap(TreeNode root) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
101. 对称二叉树
题目链接:101. 对称二叉树
思路:对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实要比较的是两棵树(这两棵树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树,如图所示:
本题遍历只能是“后序遍历”,因为要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。**正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。**但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了(其实后序也可以理解为是一种回溯)。
递归实现
- 确定递归函数的参数和返回值
因为要比较的是根节点的两个子树是否是相互翻转的,进而判断这棵树是不是对称树,所以要比较的是两棵树,参数自然也是左子树节点和右子树节点。返回值自然是bool类型。
boolean compare(TreeNode left, TreeNode right)
- 确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚,否则后面比较数值的时候就会操作空指针了。节点为空的情况有:(注意比较的其实不是左孩子和右孩子,所以下方称之为左节点右节点) - 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,return true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
- 左右都不为空,比较节点数值,不相同就return false
此时左右节点不为空,且数值也不相同的情况也处理了。
if (left == null && right != null) {
return false;
}
if (left != null && right == null) {
return false;
}
if (left == null && right == null) {
return true;
}
if (left.val != right.val) {
return false;
}
-
确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。 -
比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
-
比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
-
如果左右都对称就返回 true ,有一侧不对称就返回 false。
// 比较外侧
boolean compareOutside = compare(left.left, right.right);
// 比较内侧
boolean compareInside = compare(left.right, right.left);
return compareOutside && compareInside;
如上代码中,可以看出使用的遍历方式:左子树左右中,右子树右左中。所以这个遍历顺序也棵称为“后序遍历”(尽管不是严格的后序遍历)。
/**
* 递归法
*/
public boolean isSymmetric1(TreeNode root) {
return compare(root.left, root.right);
}
private boolean compare(TreeNode left, TreeNode right) {
// 首先排除空节点的情况
if (left == null && right != null) {
return false;
}
if (left != null && right == null) {
return false;
}
if (left == null && right == null) {
return true;
}
// 排除了空节点,再排除数值不相同的情况
if (left.val != right.val) {
return false;
}
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
// 比较外侧
boolean compareOutside = compare(left.left, right.right);
// 比较内侧
boolean compareInside = compare(left.right, right.left);
return compareOutside && compareInside;
}
/**
* 迭代法
* 使用双端队列,相当于两个栈
*/
public boolean isSymmetric2(TreeNode root) {
Deque<TreeNode> deque = new LinkedList<>();
deque.offerFirst(root.left);
deque.offerLast(root.right);
while (!deque.isEmpty()) {
TreeNode leftNode = deque.pollFirst();
TreeNode rightNode = deque.pollLast();
if (leftNode == null && rightNode == null) {
continue;
}
// if (leftNode == null && rightNode != null) {
// return false;
// }
// if (leftNode != null && rightNode == null) {
// return false;
// }
// if (leftNode.val != rightNode.val) {
// return false;
// }
// 以上三个判断条件合并
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
deque.offerFirst(leftNode.left);
deque.offerFirst(leftNode.right);
deque.offerLast(rightNode.right);
deque.offerLast(rightNode.left);
}
return true;
}
/**
* 迭代法
* 使用普通队列
*/
public boolean isSymmetric3(TreeNode root) {
Queue<TreeNode> deque = new LinkedList<>();
deque.offer(root.left);
deque.offer(root.right);
while (!deque.isEmpty()) {
TreeNode leftNode = deque.poll();
TreeNode rightNode = deque.poll();
if (leftNode == null && rightNode == null) {
continue;
}
// if (leftNode == null && rightNode != null) {
// return false;
// }
// if (leftNode != null && rightNode == null) {
// return false;
// }
// if (leftNode.val != rightNode.val) {
// return false;
// }
// 以上三个判断条件合并
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
// 这里顺序与使用Deque不同
deque.offer(leftNode.left);
deque.offer(rightNode.right);
deque.offer(leftNode.right);
deque.offer(rightNode.left);
}
return true;
}