文章目录
- 前言
- 二叉树
- 1、BM23 二叉树的前序遍历
- 2、BM24 二叉树的中序遍历
- 3、BM25 二叉树的后序遍历
- 4、BM26 求二叉树的层序遍历
- 5、BM27 按之字形顺序打印二叉树
- 6、BM28 二叉树的最大深度
- 7、BM29 二叉树中和为某一值的路径(一)
- ==8、BM30 二叉搜索树与双向链表==
- 9、BM31 对称的二叉树
- 10、BM32 合并二叉树
- 11、BM33 二叉树的镜像
- 12、BM34 判断是不是二叉搜索树
- ==13、BM35 判断是不是完全二叉树==
- ==14、BM36 判断是不是平衡二叉树==
- ==15、BM37 二叉搜索树的最近公共祖先==
- 16、BM38 在二叉树中找到两个节点的最近公共祖先
- ==17、BM39 序列化二叉树==
- 18、BM40 重建二叉树
- 19、BM41 输出二叉树的右视图
- 其它
- 总结:
前言
提示:这里可以添加本文要记录的大概内容:
本文章记录自己刷牛客网算法面试必刷TOP101–二叉树部分类容,参见牛客网地址:面试必刷TOP101–二叉树,总共有19道题目。
提示:以下是本篇文章正文内容
二叉树
1、BM23 二叉树的前序遍历
题目:
- 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
代码实现:
public class BM23 {
/**
* @param root : 二叉树根节点
* @return 返回前序遍历数组
*/
public int[] preorderTraversal(TreeNode root) {
// write code here
list = new ArrayList<>();
dfs(root);
return list.stream().mapToInt(Integer::intValue).toArray();
}
List<Integer> list;
// 前序遍历 中左右
void dfs(TreeNode root) {
if (root == null) {
return;
}
list.add(root.val);
dfs(root.left);
dfs(root.right);
}
}
2、BM24 二叉树的中序遍历
题目:
- 给定一个二叉树的根节点root,返回它的中序遍历结果。
代码实现:
public class BM24 {
/**
* @param root
* @return
*/
public int[] inorderTraversal(TreeNode root) {
// write code here
list = new ArrayList<>();
dfs(root);
return list.stream().mapToInt(x -> x.intValue()).toArray();
}
List<Integer> list;
// 中序 左中右
void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
list.add(root.val);
dfs(root.right);
}
}
3、BM25 二叉树的后序遍历
题目:
- 给定一个二叉树,返回他的后序遍历的序列。
代码实现:
public class BM25 {
public int[] postorderTraversal(TreeNode root) {
// write code here
list = new ArrayList<>();
dfs(root);
return list.stream().mapToInt(Integer::intValue).toArray();
}
List<Integer> list;
void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
dfs(root.right);
list.add(root.val);
}
}
4、BM26 求二叉树的层序遍历
题目:
- 给定一个二叉树,返回该二叉树层序遍历的结果,(从左到右,一层一层地遍历)
代码:
public class BM26 {
public ArrayList<ArrayList<Integer>> levelOrder(TreeNode root) {
// write code here
// 二叉树的层序遍历,需要借助队列辅助
Deque<TreeNode> deque = new LinkedList<>();// 双端队列
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
// 不为空加入到队列中
if (root != null) {
deque.addLast(root);
}
while (!deque.isEmpty()) {
// 获取当前层节点个数
int size = deque.size();
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = deque.pollFirst();
if (node.left != null) {
deque.addLast(node.left);
}
if (node.right != null) {
deque.addLast(node.right);
}
list.add(node.val);
}
ans.add(list);
}
return ans;
}
}
5、BM27 按之字形顺序打印二叉树
题目:
- 给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)。
代码:
public class BM27 {
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
// 辅助双端队列
Deque<TreeNode> deque = new LinkedList<>();
if (pRoot != null) {
deque.addLast(pRoot);
}
while (!deque.isEmpty()) {
int size = deque.size();
ArrayList<Integer> odd = new ArrayList<>();
// 奇数层
for (int i = 0; i < size; i++) {
TreeNode node = deque.pollFirst();
odd.add(node.val);
if (node.left != null) {
deque.addLast(node.left);
}
if (node.right != null) {
deque.addLast(node.right);
}
}
ans.add(odd);
if (deque.isEmpty()) {
break;
}
// 偶数层
size = deque.size();
ArrayList<Integer> even = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = deque.pollLast();
even.add(node.val);
if (node.right != null) {
deque.addFirst(node.right);
}
if (node.left != null) {
deque.addFirst(node.left);
}
}
ans.add(even);
}
return ans;
}
}
6、BM28 二叉树的最大深度
题目:
- 求给定二叉树的最大深度,深度是指树的根节点到任一叶子节点路径上节点的数量。
- 最大深度是所有叶子节点的深度的最大值。
- (注:叶子节点是指没有子节点的节点。)
代码:
public class BM28 {
/**
* @param root :二叉树的根节点
* @return 返回二叉树的最大深度
*/
public int maxDepth(TreeNode root) {
// write code here
if (root == null) {
return 0;
}
// 左子树的最大深度
int left = maxDepth(root.left);
// 右子树的最大深度
int right = maxDepth(root.right);
// 当前这棵树的最大深度
return Math.max(left, right) + 1;
}
}
7、BM29 二叉树中和为某一值的路径(一)
题目:
- 给定一个二叉树root和一个值 sum ,判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。
- 该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点;
- 叶子节点是指没有子节点的节点;
- 路径只能从父节点到子节点,不能从子节点到父节点;
- 总节点数目为n;
代码:
public class BM29 {
/**
* @param root : 二叉树的头节点
* @param sum : 目标值和
* @return 如果能在二叉树中,找到从头节点到叶子节点的一条路径和为目标值,返回true
*/
public boolean hasPathSum(TreeNode root, int sum) {
// write code here
if (root == null) {
return false;
}
if (root.left == null && root.right == null && root.val == sum) {
return true;
}
boolean left = hasPathSum(root.left, sum - root.val);
boolean right = hasPathSum(root.right, sum - root.val);
return left || right;
}
}
8、BM30 二叉搜索树与双向链表
题目:
- 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。
- 数据范围:输入二叉树的节点数: 0≤n≤1000,二叉树中每个节点的值 :0≤val≤1000
- 要求:空间复杂度:O(1)(即在原树上操作),时间复杂度 :O(n)
- 注意:
- 要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针要指向前驱,树中节点的右指针需要指向后继;
- 返回链表中的第一个节点的指针;
- 函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构;
- 你不用输出双向链表,程序会根据你的返回值自动打印输出;
思路:
- 我不会,参考视频地址: 36. 二叉搜索树与双向链表 | 基础功。
- 无语啊,是采用一个链表存储中序遍历的所有节点,然后将链表中的全部节点进行遍历,设置指针。
代码实现:
public class BM30 {
/**
* @param pRootOfTree : 二叉搜索树的根节点
* @return 返回转为链表的头节点
*/
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) {
return null;
}
// 如果借助队列实现
Queue<TreeNode> queue = new LinkedList<>();
dfs(pRootOfTree, queue);
TreeNode head = queue.poll();
TreeNode pre = head;
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
pre.right = cur;
cur.left = pre;
pre = cur;
}
return head;
}
void dfs(TreeNode root, Queue<TreeNode> queue) {
if (root == null) {
return;
}
dfs(root.left, queue);
queue.add(root);
dfs(root.right, queue);
}
}
9、BM31 对称的二叉树
题目:
- 给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)。
代码实现:
public class BM31 {
/**
* @param pRoot :二叉树的根节点
* @return 判断这颗二叉树是否为对称二叉树
*/
boolean isSymmetrical(TreeNode pRoot) {
// base case
if (pRoot == null) {
return true;
}
return is(pRoot.left, pRoot.right);
}
// 判断一颗树的左子树和右子树是否是镜像的
boolean is(TreeNode left, TreeNode right) {
// base case
if (left == null && right == null) {
return true;
}
if (left == null) {
return false;
}
if (right == null) {
return false;
}
// 后序遍历
boolean b1 = is(left.left, right.right);
boolean b2 = is(left.right, right.left);
return b1 && b2 && left.val == right.val;
}
}
10、BM32 合并二叉树
题目:
- 已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。
代码:
public class BM32 {
/**
* @param t1 : 二叉树1的根节点
* @param t2 : 二叉树2的根节点
* @return 返回新的二叉树的节点
*/
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
// write code here
// 采用先序遍历
// base case 1
if (t1 == null && t2 == null) {
return null;
}
// base case 2
if (t1 == null) {
return t2;
}
// base case 3
if (t2 == null) {
return t1;
}
// base case 4
TreeNode head = new TreeNode(t1.val + t2.val);
head.left = mergeTrees(t1.left, t2.left);
head.right = mergeTrees(t1.right, t2.right);
return head;
}
}
11、BM33 二叉树的镜像
题目:
- 操作给定的二叉树,将其变换为源二叉树的镜像。
- 数据范围:二叉树的节点数 :0≤n≤1000 , 二叉树每个节点的值 :0≤val≤1000。
- 要求: 空间复杂度 O(n) 。本题也有原地操作,即空间复杂度: O(1) 的解法,时间复杂度 :O(n)。
代码:
public class BM33 {
/**
* @param pRoot : 二叉树的根节点
* @return true:二叉树为镜像二叉树
*/
public TreeNode Mirror(TreeNode pRoot) {
// write code here
if (pRoot == null) {
return null;
}
TreeNode left = Mirror(pRoot.left);
TreeNode right = Mirror(pRoot.right);
pRoot.left = right;
pRoot.right = left;
return pRoot;
}
}
12、BM34 判断是不是二叉搜索树
题目:
- 给定一个二叉树根节点,请你判断这棵树是不是二叉搜索树。
- 二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。
代码:
public class BM34 {
// 涉及到二叉搜索树就用中序遍历
public boolean isValidBST(TreeNode root) {
// write code here
// 判断当前元素和之前的元素相比,谁更大
if (root == null) {
return true;
}
boolean left = isValidBST(root.left);
if (pre != null) {
if (root.val < pre.val) {
return false;
}
}
pre = root;
boolean right = isValidBST(root.right);
return left && right;
}
TreeNode pre = null;
}
13、BM35 判断是不是完全二叉树
题目:
- 给定一个二叉树,确定他是否是一个完全二叉树。
- 完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)。
- 数据范围:节点数满足 :1≤n≤100 。
代码:
public class BM35 {
public boolean isCompleteTree(TreeNode root) {
// write code here
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) {
queue.add(root);
}
boolean leafNode = false;
while (!queue.isEmpty()) {
TreeNode poll = queue.poll();
if (poll == null) {
leafNode = true;
continue;
}
if (leafNode) {
return false;
}
queue.offer(poll.left);
queue.offer(poll.right);
}
return true;
}
}
14、BM36 判断是不是平衡二叉树
题目:
- 输入一棵节点数为 n 二叉树,判断该二叉树是否是平衡二叉树。
- 在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。
- 平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
代码:
public class BM36 {
public boolean IsBalanced_Solution(TreeNode root) {
if (root == null) {
return true;
}
int left = height(root.left);
int right = height(root.right);
if (left - right > 1 || left - right < -1) {
return false;
}
return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
}
// 得到root为根节点的树高度
int height(TreeNode root) {
if (root == null) {
return 0;
}
int left = height(root.left);
int right = height(root.right);
return Math.max(left, right) + 1;
}
}
15、BM37 二叉搜索树的最近公共祖先
题目:
- 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
- 对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q,最近公共祖先LCA(T,p,q)表示一个节点x,满足x是p和q的祖先且x的深度尽可能大。在这里,一个节点也可以是它自己的祖先.
- 二叉搜索树是若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值。
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉搜索树中。
- 数据范围:
- 3<=节点总数<=10000
- 0<=节点值<=10000
思路:
- 需要利用二叉搜索树的性质;
- 采用先序遍历,判断中间节点值是否在p和q之间;
代码:
public class BM37 {
public int lowestCommonAncestor(TreeNode root, int p, int q) {
// write code here
// 后序遍历
// p、q 为不同节点且均存在于给定的二叉搜索树中。
int small = p >= q ? q : p;
int big = small == p ? q : p;
if (isBetween(root, small, big)) {
return root.val;
} else if (root.val > big) {
return lowestCommonAncestor(root.left, p, q);
} else {
return lowestCommonAncestor(root.right, p, q);
}
}
// 判断这个头节点是否在 p 和 q 的数值之间
boolean isBetween(TreeNode head, int small, int big) {
return head.val >= small && head.val <= big;
}
}
16、BM38 在二叉树中找到两个节点的最近公共祖先
题目:
- 给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。
- 数据范围:树上节点数满足 :1≤n≤10的5次方,节点值val满足区间 [0,n)
- 要求:时间复杂度 :O(n)
- 注:本题保证二叉树中每个节点的val值均不相同。
代码:
public class BM38 {
public int lowestCommonAncestor(TreeNode root, int o1, int o2) {
// write code here
// 这种普通的二叉树,就不能像前面一道题,使用二叉搜索树的性质了,采用先序遍历,
// 叶子节点
return find(root, o1, o2).val;
}
private TreeNode find(TreeNode root, int o1, int o2) {
// base case
if (root == null || root.val == o1 || root.val == o2) {
return root;
}
// 后序遍历
TreeNode left = find(root.left, o1, o2);
TreeNode right = find(root.right, o1, o2);
if (left != null && right != null) {
return root;
} else if (left != null) {
return left;
} else {
return right;
}
}
}
17、BM39 序列化二叉树
题目:
- 请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。
- 二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)。
- 二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
思路:
- 序列化二叉树和反序列化二叉树,采用层序遍历的方法;
代码:
public class BM39 {
// 采用层序遍历
String Serialize(TreeNode root) {
// base case
if (root == null) {
return "{}";
}
Deque<TreeNode> deque = new LinkedList<>();
deque.addLast(root);
// 字符串拼接
StringBuilder sb = new StringBuilder("{");
// 层序遍历开始
while (!deque.isEmpty()) {
// 每一层
TreeNode node = deque.pollFirst();
if (node != null) {
deque.addLast(node.left);
deque.addLast(node.right);
sb.append(node.val).append(",");
} else {
sb.append("#").append(",");
}
}
sb.deleteCharAt(sb.length() - 1).append("}");
return sb.toString();
}
TreeNode Deserialize(String str) {
// base case
if (str == "{}" || str.length() <= 0) {
return null;
}
// 分割字符串
String substring = str.substring(1, str.length() - 1);
String[] arr = substring.split(",");
// 利用层序遍历
Deque<TreeNode> deque = new LinkedList<>();
TreeNode root = new TreeNode(Integer.valueOf(arr[0]));
deque.addLast(root);
// 定义一个指针遍历arr数组
int index = 1;
while (!deque.isEmpty()) {
TreeNode node = deque.pollFirst();
if (!arr[index].equals("#")) {
TreeNode left = new TreeNode(Integer.parseInt(arr[index]));
node.left = left;
// 入队
deque.addLast(left);
}
index++;
if (!arr[index].equals("#")) {
TreeNode right = new TreeNode(Integer.parseInt(arr[index]));
node.right = right;
// 入队
deque.addLast(right);
}
index++;
}
return root;
}
}
18、BM40 重建二叉树
题目:
- 给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
- 提示:
- vin.length == pre.length;
- pre 和 vin 均无重复元素;
- vin出现的元素均出现在 pre里;
- 只需要返回根结点,系统会自动输出整颗树做答案对比;
- 数据范围:
- n≤2000,节点的值 :−10000≤val≤10000;
- 要求:空间复杂度 :O(n),时间复杂度 :O(n)
代码:
public class BM40 {
public TreeNode reConstructBinaryTree(int[] pre, int[] vin) {
// 先序遍历为:中左右,中序遍历为:左中右
// 思路就是每次从先序数组中找到头节点,然后去中序数组中找到头节点对应的元素
map = new HashMap<>();
for (int i = 0; i < vin.length; i++) {
map.put(vin[i], i);
}
// 构建二叉树
return build(pre, 0, pre.length - 1, vin, 0, vin.length - 1);
}
HashMap<Integer, Integer> map;// 用来存储中序数组中元素和对应下标
TreeNode build(int[] pre, int pLeft, int pRight, int[] vin, int vLeft, int vRight) {
// base case
if (pLeft > pRight) {
return null;
}
// 根节点
TreeNode root = new TreeNode(pre[pLeft]);
// 去中序数组中找到根节点对应的元素下标
int index = map.get(pre[pLeft]);
// 根节点左子树有多少个元素
int size = index - vLeft;
// 开始递归
root.left = build(pre, pLeft + 1, pLeft + size, vin, vLeft, index - 1);
root.right = build(pre, pLeft + size + 1, pRight, vin, index + 1, vRight);
return root;
}
}
19、BM41 输出二叉树的右视图
题目:
- 请根据二叉树的前序遍历,中序遍历恢复二叉树,并打印出二叉树的右视图;
- 数据范围: 0≤n≤10000;
- 要求: 空间复杂度 :O(n),时间复杂度 :O(n);
代码:
public class BM41 {
public int[] solve(int[] xianxu, int[] zhongxu) {
// write code here
if (xianxu.length <= 0) {
return new int[]{};
}
// 根据先序和中序数组建立二叉树
map = new HashMap<>();
for (int i = 0; i < zhongxu.length; i++) {
map.put(zhongxu[i], i);
}
// 构建二叉树
TreeNode root = build(xianxu, 0, xianxu.length - 1, zhongxu, 0, zhongxu.length - 1);
// 层序遍历
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
// 结果集
ArrayList<Integer> ans = new ArrayList<>();
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (i == size - 1) {
ans.add(node.val);
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
return ans.stream().mapToInt(Integer::intValue).toArray();
}
HashMap<Integer, Integer> map;// 用来存储中序数组中元素和对应下标
TreeNode build(int[] pre, int pLeft, int pRight, int[] vin, int vLeft, int vRight) {
// base case
if (pLeft > pRight) {
return null;
}
// 根节点
TreeNode root = new TreeNode(pre[pLeft]);
// 去中序数组中找到根节点对应的元素下标
int index = map.get(pre[pLeft]);
// 根节点左子树有多少个元素
int size = index - vLeft;
// 开始递归
root.left = build(pre, pLeft + 1, pLeft + size, vin, vLeft, index - 1);
root.right = build(pre, pLeft + size + 1, pRight, vin, index + 1, vRight);
return root;
}
}
其它
1、有序数组中第K个缺失的正整数
此题为左程云老师B站账号里面发的,视频地址:来自亚马逊、谷歌算法一面面试题:有序数组中第K个缺失的正整数。
题目:
- 给你一个严格升序排列的正整数数组arr和一个整数k,请你找到这个数组里第k个缺失的正整数。
- 对应的是力扣的一道题目:1539. 第 k 个缺失的正整数;
- 1 <= arr.length <= 1000
- 1 <= arr[i] <= 1000
- 1 <= k <= 1000
思路:
- 我立马想到的是利用二分
代码:
public class Demo1539 {
/**
* @param arr : 严格递增的正整数数组
* @param k : 缺失的第k个数
* @return
*/
public int findKthPositive(int[] arr, int k) {
// base case
if (arr[0] > k) {
return k;
}
// 二分
int left = 0, right = arr.length, mid = 0, cur;
// 当 left==right 的时候就会跳出循环
while (left < right) {
mid = left + (right - left) / 2;
cur = mid < arr.length ? arr[mid] : Integer.MAX_VALUE;
// 比较
if (cur - mid - 1 >= k) {
right = mid;
} else {
left = mid + 1;
}
}
return arr[left - 1] + k - (arr[left - 1] - (left - 1) - 1);
}
}
总结:
总共写了19道题目,都是很基本的题目,主要是掌握二叉树的常见解题方法:
- 层序遍历,利用队列;
- 二叉搜索树需要利用性质,一般使用中序遍历;
- 大多数都是采用深度优先遍历,如果需要先获得子树的信息再去计算的话,采用后序遍历;