刷题神器
往期回顾
>【栈与队列】|代码随想录算法训练营第10天|150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素、【总结】
题目
二叉树理论基础
二叉树种类
-
满二叉树
除最底层叶子节点,其余所有的节点都有左右子节点
深度为k,有2^k-1个节点的二叉树
-
完全二叉树
最后一层子节点,从左到右连续的,如果有不连续的就不是完全二叉树
-
二叉搜索树
二叉搜索树是一个有序树,如果左子树不空,左子树所有的值都小于根节点的值,如果右子树不空,右子树所有的节点都大于根节点,左右子树也是二叉排序树
-
平衡二叉搜索树
左右子树的高度绝对值不超过1, 并且左右子树也是平衡二叉树
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
链式存储是通过链表左右子针的方式存储的
顺序存储,用的数组方式存储的,如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
二叉树的遍历方式
- 深度优先遍历
- 前序遍历(递归,迭代法) 中 左 右
- 中序遍历(递归,迭代法) 左 中 右
- 后续遍历(递归,迭代法) 左 右 中
- 广度优先遍历
- 层次遍历(迭代法)
二叉树的定义
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
二叉树的递归遍历
题目:1.前序遍历
2.中序遍历
-
学后思路
递归三要素 :1.确定递归函数的参数和返回值 2.确定终止条件 3.确定单层递归的逻辑
确定循环的顺序
解法:
// 前序遍历 中 左 右
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList();
preOrder(result, root);
return result;
}
public void preOrder(List<Integer> result, TreeNode root) {
if (root == null) {
return;
}
result.add(root.val);
preOrder(result, root.left);
preOrder(result, root.right);
}
}
// 后序遍历 左 右 中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList();
postOrder(result, root);
return result;
}
public void postOrder(List<Integer> result, TreeNode root) {
if (root == null) {
return;
}
postOrder(result, root.left);
postOrder(result, root.right);
result.add(root.val);
}
}
// 中序遍历 左 中 右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList();
inOrder(result, root);
return result;
}
public void inOrder(List<Integer> result, TreeNode root) {
if (root == null) {
return;
}
inOrder(result, root.left);
result.add(root.val);
inOrder(result, root.right);
}
}
- 题目总结
- 二叉树的遍历,注意前中后的排序,注意判断条件,什么时候跳出
二叉树的迭代遍历
题目:1.前序遍历
2.中序遍历
视频讲解-中
- 学后思路
迭代遍历主要是使用栈来实现遍历,前序遍历和后序遍历只需要做反转逻辑,中序遍历需要使用指针
解法一:
// 前序遍历 中 左 右
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList();
if (root == null)
return result;
Stack<TreeNode> stack = new Stack();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null)
stack.push(node.right);
if (node.left != null)
stack.push(node.left);
}
return result;
}
}
// 后续遍历 左右中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList();
if (root == null)
return result;
Stack<TreeNode> stack = new Stack();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null)
stack.push(node.left);
if (node.right != null)
stack.push(node.right);
}
Collections.reverse(result);
return result;
}
}
// 中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList();
Stack<TreeNode> stack = new Stack();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
// 处理左指针
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
// 当左指针为null的时候
cur = stack.pop();
result.add(cur.val);
// 判断右节点
cur = cur.right;
}
}
return result;
}
}
- 题目总结
- 使用迭代遍历,前序和后序要相对简单,前序最简单,后续需要再前序的基础上进行反转
- 中序遍历需要配合指针只用,注意判断条件,什么时候弹出,什么时候进栈
二叉树的统一迭代法
- 学后思路
使用填充空节点的方法来统一三种迭代方法
解法一:
暂时不处理
题目总结
- 主要注意如何补充空节点,这样就是为了补充统一格式
二叉树层序遍历登场
- 学后思路
层序遍历,广度优先搜索,借用队列
102.二叉树的层序遍历
题目:题目链接
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> deque = new LinkedList<>();
if (root == null)
return result;
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
List<Integer> rs = new ArrayList<>();
while (size > 0) {
TreeNode pop = deque.poll();
rs.add(pop.val);
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
result.add(rs);
}
return result;
}
}
题目总结
- 注意队列的size判断,注意进队出队
107. 二叉树的层序遍历 II
题目:题目链接
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> deque = new LinkedList<>();
if (root == null)
return result;
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
List<Integer> rs = new ArrayList<>();
while (size > 0) {
TreeNode pop = deque.poll();
rs.add(pop.val);
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
result.add(rs);
}
Collections.reverse(result);
return result;
}
}
题目总结
- 从下至上遍历,只需要将正序遍历的结果反转即可
199.二叉树的右视图
题目:题目链接
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> rs = new ArrayList<>();
Queue<TreeNode> deque = new LinkedList<>();
if (root == null)
return rs;
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
while (size > 0) {
TreeNode pop = deque.poll();
if (size == 1) {
rs.add(pop.val);
}
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
}
return rs;
}
}
题目总结
- 从右侧看,只保存最右侧节点, 所以要判断每一层的最后一个节点加入到结果集
637. 二叉树的层平均值
题目:题目链接
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> result = new ArrayList<>();
Queue<TreeNode> deque = new LinkedList<>();
if (root == null)
return result;
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
List<Integer> rs = new ArrayList<>();
while (size > 0) {
TreeNode pop = deque.poll();
rs.add(pop.val);
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
// 注意这个位置的类型转换
Long rowTotal = rs.stream().mapToLong(r -> Long.valueOf(r)).reduce(Long::sum).getAsLong();
Double ave = rowTotal / Double.valueOf(rs.size());
result.add(ave);
}
return result;
}
}
题目总结
- 这道题的重点是关注除法,每一行的和可能超过int的最大值,所以要用long,size要转换成double
429. N 叉树的层序遍历
题目:题目链接
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> result = new ArrayList<>();
Queue<Node> deque = new LinkedList<>();
if (root == null)
return result;
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
List<Integer> rs = new ArrayList<>();
while (size > 0) {
Node pop = deque.poll();
rs.add(pop.val);
// 注意节点没有左右子树,是多子树
if (pop.children != null) {
for (Node no : pop.children) {
deque.add(no);
}
}
size--;
}
result.add(rs);
}
return result;
}
}
题目总结
- 注意节点,不在分左右子树,有多子节点,所以处理子节点的时候要遍历处理,不是判断左右子树
515. 在每个树行中找最大值
题目:题目链接
class Solution {
public List<Integer> largestValues(TreeNode root) {
List<Integer> result = new ArrayList<>();
Queue<TreeNode> deque = new LinkedList<>();
if (root == null)
return result;
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
List<Integer> rs = new ArrayList<>();
while (size > 0) {
TreeNode pop = deque.poll();
rs.add(pop.val);
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
Integer max = rs.stream().max(Comparator.comparing(Integer::intValue)).get();
result.add(max);
}
return result;
}
}
题目总结
- 取每一层的最大值,可以全部存起来再判断最大值,也可以再遍历的过程中判断最大值
116. 填充每个节点的下一个右侧节点指针
题目:题目链接
class Solution {
public Node connect(Node root) {
if (root == null)
return root;
Queue<Node> deque = new LinkedList<>();
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
//List<Node> rs = new ArrayList<>();
while (size > 0) {
Node pop = deque.poll();
// rs.add(pop);
if(size > 1){
pop.next = deque.peek();
}
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
//if (rs.size() == 1)
// continue;
//int slow = 0;
//int fast = 1;
//while (fast < rs.size()) {
// rs.get(slow).next = rs.get(fast);
// slow++;
// fast++;
//}
}
return root;
}
}
题目总结
- 这个题目有两种做法,在弹出每一个的时候判断是不是最后一个,如果不是最后一个可以当前队列的第一个付给next,也可以全部存在一个list中,然后用快慢指针赋值next
117. 填充每个节点的下一个右侧节点指针 II
题目:题目链接
class Solution {
public Node connect(Node root) {
if (root == null)
return root;
Queue<Node> deque = new LinkedList<>();
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
//List<Node> rs = new ArrayList<>();
while (size > 0) {
Node pop = deque.poll();
// rs.add(pop);
if(size > 1){
pop.next = deque.peek();
}
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
//if (rs.size() == 1)
// continue;
//int slow = 0;
//int fast = 1;
//while (fast < rs.size()) {
// rs.get(slow).next = rs.get(fast);
// slow++;
// fast++;
//}
}
return root;
}
}
题目总结
- 结题思路与116类似,代码一样
104. 二叉树的最大深度
题目:题目链接
class Solution {
public int maxDepth(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> deque = new LinkedList<>();
if (root == null)
return result.size();
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
List<Integer> rs = new ArrayList<>();
while (size > 0) {
TreeNode pop = deque.poll();
rs.add(pop.val);
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
result.add(rs);
}
return result.size();
}
}
题目总结
- 取最大深度,换个思路就是取有多少层,我们把结果收集起来以后,list的长度就是层数,当然也可以不用记录list,直接在循环结束的时候计数也可以
111. 二叉树的最小深度
题目:题目链接
class Solution {
public int minDepth(TreeNode root) {
if (root == null)
return 0;
Queue<TreeNode> deque = new LinkedList<>();
int depth = 1;
deque.add(root);
while (!deque.isEmpty()) {
int size = deque.size();
while (size > 0) {
TreeNode pop = deque.poll();
// 判断最短路径
if (pop.left == null && pop.right == null) {
return depth;
}
if (pop.left != null)
deque.add(pop.left);
if (pop.right != null)
deque.add(pop.right);
size--;
}
depth++;
}
return 0;
}
}
题目总结
- 最小深度,可以同等为从左往右看,第一个没有子节点的高度值,从左往右,当遇到第一个左孩子和右孩子都不存在的节点,那当前节点高度就是最小高度