03 二叉树
3.1 BM23、24、25 二叉树的前序、中序、后序遍历
BM23 前序遍历
描述
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
数据范围:二叉树的节点数量满足 0 ≤ n ≤ 100 0 \le n \le 100 0≤n≤100 ,二叉树节点的值满足 0 ≤ v a l ≤ 100 0 \le val \le 100 0≤val≤100,树的各节点的值各不相同
示例 1:
示例1
输入:{1,#,2,3}
返回值:[1,2,3]
思路
前序遍历?简单来说就是“根左右”
可以用递归处理:
终止条件: 当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。
返回值: 每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。
本级任务: 每个子问题优先访问这棵子树的根节点,然后递归进入左子树和右子树。
import java.util.*;
public class Solution {
public void preorder(List<Integer> list, TreeNode root){
//遇到空节点则返回
if(root == null)
return;
//先遍历根节点
list.add(root.val);
//再去左子树
preorder(list, root.left);
//最后去右子树
preorder(list, root.right);
}
public int[] preorderTraversal (TreeNode root) {
//添加遍历结果的数组
List<Integer> list = new ArrayList();
//递归前序遍历
preorder(list, root);
//返回的结果
int[] res = new int[list.size()];
for(int i = 0; i < list.size(); i++)
res[i] = list.get(i);
return res;
}
}
BM24 中序
···
public int[] inorderTraversal (TreeNode root) {
// write code here
List<Integer> list = new ArrayList<>();
inorder(list , root);
int[] res = new int[list.size()];
for(int i=0 ; i<list.size() ; i++){
res[i] = list.get(i);
}
return res;
}
public void inorder (List<Integer> list , TreeNode root) {
// write code here
if(root==null ) return;
inorder(list , root.left);
list.add(root.val);
inorder(list , root.right);
}
}
BM25 后序
···java
public int[] postorderTraversal (TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root==null)
return new int[0];
postor(list,root);
int[] res = new int[list.size()];
for(int i=0 ; i<list.size(); i++){
res[i] = list.get(i);
}
return res;
}
public void postor(List<Integer> list,TreeNode root){
if(root==null) return;
postor(list,root.left);
postor(list,root.right);
list.add(root.val);
}
3.2 BM26\27\35 二叉树的层次遍历,之字打印、是否为完全二叉树
BM26 层次
思路 BFS
就像题目描述所说,从左到右,一层一层的遍历,即BFS遍历。
首先我们定义一个队列,然后将根节点入队,当队列不为空的时候,需要进行以下两个操作:
1)求出当前队列的长度大小len 。
2)取出队列前len个节点,每取出一个节点,就把对应节点的左右孩子入队(前提孩子不为空),然后重复这一过程直到队列为空后输出结果。
详细操作流程看下图
import java.util.*;
public class Solution {
/**
*
* @param root TreeNode类
* @return int整型ArrayList<arraylist<>>
*/
public ArrayList<arraylist<integer>> levelOrder (TreeNode root) {
ArrayList<arraylist<integer>> res = new ArrayList();//用于返回最后的结果
if(root == null) return res;//如果根节点为空就返回结果
Queue<treenode> q = new LinkedList<treenode>();//用于存储每一层的节点
q.add(root);
while(!q.isEmpty()){
int n = q.size();
ArrayList<integer> temp = new ArrayList<>();//用于存储当前遍历这一层的节点
for(int i = 0;i < n;i ++){
TreeNode node = q.poll();//取出队列的第一个元素
temp.add(node.val);//将队头元素保存起来
if(node.left != null) q.add(node.left);//左孩子如果不为空就进队列
if(node.right != null) q.add(node.right);//右孩子如果不为空就进队列
}
res.add(temp);//将这一层的节点数里面据保存到res
}
return res;
}
}
BM27 按之字形顺序打印二叉树
BFS+数组反转
if(flag%2==1){
Collections.reverse(row);
}
flag++;
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
TreeNode head = pRoot;
ArrayList<ArrayList<Integer> > res = new ArrayList<ArrayList<Integer>>();
if(head == null)
//如果是空,则直接返回空list
return res;
//队列存储,进行层次遍历
Queue<TreeNode> temp = new LinkedList<TreeNode>();
temp.offer(head);
TreeNode p;
boolean flag = true;
while(!temp.isEmpty()){
//记录二叉树的某一行
ArrayList<Integer> row = new ArrayList<Integer>();
int n = temp.size();
//奇数行反转,偶数行不反转
flag = !flag;
//因先进入的是根节点,故每层节点多少,队列大小就是多少
for(int i = 0; i < n; i++){
p = temp.poll();
row.add(p.val);
//若是左右孩子存在,则存入左右孩子作为下一个层次
if(p.left != null)
temp.offer(p.left);
if(p.right != null)
temp.offer(p.right);
}
//奇数行反转,偶数行不反转
if(flag)
Collections.reverse(row);
res.add(row);
}
return res;
}
}
BM35 判断是不是完全二叉树
important
import java.util.*;
public class Solution {
public boolean isCompleteTree (TreeNode root) {
//空树一定是完全二叉树
if(root == null)
return true;
//辅助队列
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
TreeNode cur;
//定义一个首次出现的标记位
boolean notComplete = false;
while(!queue.isEmpty()){
cur = queue.poll();
//标记第一次遇到空节点
if(cur == null){
notComplete = true;
continue;
}
//后续访问已经遇到空节点了,说明经过了叶子
if(notComplete)
return false;
queue.offer(cur.left);
queue.offer(cur.right);
}
return true;
}
}
3.3 BM28 二叉树的最大深度、BM29、31、32、33、36、39、41
BM28 二叉树的最大深度
###思路:层序遍历
import java.util.*;
public class Solution {
public int maxDepth (TreeNode root) {
//空节点没有深度
if(root == null)
return 0;
//队列维护层次后续节点
Queue<TreeNode> q = new LinkedList<TreeNode>();
//根入队
q.offer(root);
//记录深度
int res = 0;
//层次遍历
while(!q.isEmpty()){
//记录当前层有多少节点
int n = q.size();
//遍历完这一层,再进入下一层
for(int i = 0; i < n; i++){
TreeNode node = q.poll();
//添加下一层的左右节点
if(node.left != null)
q.offer(node.left);
if(node.right != null)
q.offer(node.right);
}
//深度加1
res++;
}
return res;
}
}
BM29 二叉树中和为某一值的路径(一)
思路1:递归
具体做法:
step 1:每次检查遍历到的节点是否为空节点,空节点就没有路径。
step 2:再检查遍历到是否为叶子节点,且当前sum值等于节点值,说明可以刚好找到。
step 3:检查左右子节点是否可以有完成路径的,如果任意一条路径可以都返回true,因此这里选用两个子节点递归的或
import java.util.*;
public class Solution {
public boolean hasPathSum (TreeNode root, int sum) {
//空节点找不到路径
if(root == null)
return false;
//叶子节点,且路径和为sum
if(root.left == null && root.right == null && sum - root.val == 0)
return true;
//递归进入子节点
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
思路2:深度优先搜索DFS
import java.util.*;
public class Solution {
public boolean hasPathSum (TreeNode root, int sum) {
//空节点找不到路径
if(root == null)
return false;
//栈辅助深度优先遍历
Stack<TreeNode> s1 = new Stack<TreeNode>();
//跟随s1记录到相应节点为止的路径和
Stack<Integer> s2 = new Stack<Integer>();
s1.push(root);
s2.push(root.val);
while(!s1.isEmpty()){
//弹出相应节点
TreeNode temp = s1.pop();
//弹出到该点为止的当前路径和
int cur_sum = s2.pop();
//叶子节点且当前路径和等于sum
if(temp.left == null && temp.right == null && cur_sum == sum)
return true;
//左节点及路径和入栈
if(temp.left != null){
s1.push(temp.left);
s2.push(cur_sum + temp.left.val);
}
//右节点及路径和入栈
if(temp.right != null){
s1.push(temp.right);
s2.push(cur_sum + temp.right.val);
}
}
return false;
}
}
BM31 对称的二叉树
1.递归recursion
public class Solution {
boolean recursion(TreeNode root1, TreeNode root2){
//可以两个都为空
if(root1 == null && root2 == null)
return true;
//只有一个为空或者节点值不同,必定不对称
if(root1 == null || root2 == null || root1.val != root2.val)
return false;
//每层对应的节点进入递归比较
return recursion(root1.left, root2.right) && recursion(root1.right, root2.left);
}
boolean isSymmetrical(TreeNode pRoot) {
return recursion(pRoot, pRoot);
}
}
2.BFS广度优先、层次遍历
import java.util.*;
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
//空树为对称的
if(pRoot == null)
return true;
//辅助队列用于从两边层次遍历
Queue<TreeNode> q1 = new LinkedList<TreeNode>();
Queue<TreeNode> q2 = new LinkedList<TreeNode>();
q1.offer(pRoot.left);
q2.offer(pRoot.right);
while(!q1.isEmpty() && !q2.isEmpty()){
//分别从左边和右边弹出节点
TreeNode left = q1.poll();
TreeNode right = q2.poll();
//都为空暂时对称
if(left == null && right == null)
continue;
//某一个为空或者数字不相等则不对称
if(left == null || right == null || left.val != right.val)
return false;
//从左往右加入队列
q1.offer(left.left);
q1.offer(left.right);
//从右往左加入队列
q2.offer(right.right);
q2.offer(right.left);
}
//都检验完都是对称的
return true;
}
}
BM32 合并二叉树
已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。
1 递归
import java.util.*;
public class Solution {
public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
//若只有一个节点返回另一个,两个都为null自然返回null
if (t1 == null)
return t2;
if (t2 == null)
return t1;
//根左右的方式递归
TreeNode head = new TreeNode(t1.val + t2.val);
head.left = mergeTrees(t1.left, t2.left);
head.right = mergeTrees(t1.right, t2.right);
return head;
}
}
2 层次、BFS
import java.util.*;
public class Solution {
public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
//若只有一个节点返回另一个,两个都为null自然返回null
if (t1 == null)
return t2;
if (t2 == null)
return t1;
//合并根节点
TreeNode head = new TreeNode(t1.val + t2.val);
//连接后的树的层次遍历节点
Queue<TreeNode> q = new LinkedList<TreeNode>();
//分别存两棵树的层次遍历节点
Queue<TreeNode> q1 = new LinkedList<TreeNode>();
Queue<TreeNode> q2 = new LinkedList<TreeNode>();
q.offer(head);
q1.offer(t1);
q2.offer(t2);
while (!q1.isEmpty() && !q2.isEmpty()) {
TreeNode node = q.poll();
TreeNode node1 = q1.poll();
TreeNode node2 = q2.poll();
TreeNode left1 = node1.left;
TreeNode left2 = node2.left;
TreeNode right1 = node1.right;
TreeNode right2 = node2.right;
if(left1 != null || left2 != null){
//两个左节点都存在
if(left1 != null && left2 != null){
TreeNode left = new TreeNode(left1.val + left2.val);
node.left = left;
//新节点入队列
q.offer(left);
q1.offer(left1);
q2.offer(left2);
//只连接一个节点
}else if(left1 != null)
node.left = left1;
else
node.left = left2;
}
if(right1 != null || right2 != null){
//两个右节点都存在
if(right1 != null && right2 != null) {
TreeNode right = new TreeNode(right1.val + right2.val);
node.right = right;
//新节点入队列
q.offer(right);
q1.offer(right1);
q2.offer(right2);
//只连接一个节点
}else if(right1 != null)
node.right = right1;
else
node.right = right2;
}
}
return head;
}
}
BM33 二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
数据范围:二叉树的节点数 0≤n≤1000 , 二叉树每个节点的值 0≤val≤1000
要求: 空间复杂度 O(n)。本题也有原地操作,即空间复杂度 O(1)的解法,时间复杂度 O(n)
1递归
import java.util.*;
public class Solution {
public TreeNode Mirror (TreeNode pRoot) {
//空树返回
if(pRoot == null)
return null;
//先递归子树
TreeNode left = Mirror(pRoot.left);
TreeNode right = Mirror(pRoot.right);
//交换
pRoot.left = right;
pRoot.right = left;
return pRoot;
}
}
时间空间O(n)
2辅助栈
import java.util.*;
public class Solution {
public TreeNode Mirror (TreeNode pRoot) {
//空树
if(pRoot == null)
return null;
//辅助栈
Stack<TreeNode> s = new Stack<TreeNode>();
//根节点先进栈
s.push(pRoot);
while (!s.isEmpty()){
TreeNode node = s.pop();
//左右节点入栈
if(node.left != null)
s.push(node.left);
if(node.right != null)
s.push(node.right);
//交换左右
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
return pRoot;
}
}
时间空间O(n)
BM36 判断是不是平衡二叉树
自底向上
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
//空树也是平衡二叉树
if(root == null)
return true;
return getdepth(root) != -1;
}
public int getdepth(TreeNode root) {
if(root == null)
return 0;
//递归计算当前root左右子树的深度差
int left = getdepth(root.left);
//当前节点左子树不平衡,则该树不平衡
if(left < 0)
return -1;
int right = getdepth(root.right);
//当前节点右子树不平衡,则该树不平衡
if(right < 0)
return -1;
//计算深度差
return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
}
}
时间复杂度:O(n),其中n为树的节点数,一次递归遍历二叉树所有节点
空间复杂度:O(n),最坏情况下递归栈的深度为n
自顶向下是时间
O
(
n
2
)
O(n^2)
O(n2)
link
BM39 序列化二叉树(难)
BM41 输出二叉树的右视图(中等偏难)
link
请根据二叉树的前序遍历,中序遍历恢复二叉树,并打印出二叉树的右视图
方法二:哈希表优化的递归建树+层次遍历(扩展思路)
step 1:首先检查两个遍历序列的大小,若是为0,则空树不用打印。
step 2:遍历前序遍历序列,用哈希表将中序遍历中的数值与前序遍历的下标建立映射。
step 3:按照方法一递归划分子树,只是可以利用哈希表直接在中序遍历中定位根节点的位置。
step 4:建立队列辅助层次遍历,根节点先进队。
step 5:用一个size变量,每次进入一层的时候记录当前队列大小,等到size为0时,便到了最右边,记录下该节点元素。
import java.util.*;
public class Solution {
public Map<Integer, Integer> index;
//建树函数
//四个int参数分别是前序最左节点下标,前序最右节点下标
//中序最左节点下标,中序最右节点坐标
public TreeNode buildTree(int[] xianxu, int l1, int r1, int[] zhongxu, int l2, int r2){
if(l1 > r1 || l2 > r2)
return null;
//前序遍历中的第一个节点就是根节点
int xianxu_root = l1;
//在中序遍历中定位根节点
int zhongxu_root = index.get(xianxu[xianxu_root]);
TreeNode root = new TreeNode(xianxu[xianxu_root]);
//得到左子树中的节点数目
int leftsize = zhongxu_root - l2;
root.left = buildTree(xianxu, l1 + 1, l1 + leftsize, zhongxu, l2, zhongxu_root - 1);
root.right = buildTree(xianxu, l1 + leftsize + 1, r1, zhongxu, zhongxu_root + 1, r2);
return root;
}
//层次遍历
public ArrayList<Integer> rightSideView(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(root);
while(!q.isEmpty()){
//队列中的大小即是这一层的节点树
int size = q.size();
while(size-- != 0){
TreeNode temp = q.poll();
if(temp.left != null)
q.offer(temp.left);
if(temp.right != null)
q.offer(temp.right);
//最右元素
if(size == 0)
res.add(temp.val);
}
}
return res;
}
public int[] solve (int[] xianxu, int[] zhongxu) {
index = new HashMap<Integer, Integer>();
//空节点
if(xianxu.length == 0)
return new int[0];
//用哈希表标记中序节点在前序中的位置
for(int i = 0; i < xianxu.length; i++)
index.put(zhongxu[i], i);
//建树
TreeNode root = buildTree(xianxu, 0, xianxu.length - 1, zhongxu, 0, zhongxu.length - 1);
//获取右视图输出
ArrayList<Integer> temp = rightSideView(root);
//转化为数组
int[] res = new int[temp.size()];
for(int i = 0; i < temp.size(); i++)
res[i] = temp.get(i);
return res;
}
}
复杂度分析:
时间复杂度:O(n)O(n)O(n),其中nnn为二叉树节点个数,每个节点访问一次,哈希表直接访问数组中的元素
空间复杂度:O(n)O(n)O(n),递归栈深度、哈希表、队列的空间都为O(n)O(n)O(n)
3.4 二叉搜索树BM30、34、37、38
知识点2:二叉搜索树
二叉搜索树是一种特殊的二叉树,它的每个节点值大于它的左子节点,且大于全部左子树的节点值,小于它右子节点,且小于全部右子树的节点值。因此二叉搜索树一定程度上算是一种排序结构。
*二叉搜索树的特性就是中序遍历是递增序。
BM30 二叉搜索树与双向链表
方法一:递归中序遍历(推荐使用)
具体做法:
step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre)。
step 2:首先递归到最左,初始化head与pre。
step 3:然后处理中间根节点,依次连接pre与当前节点,连接后更新pre为当前节点。
step 4:最后递归进入右子树,继续处理。
step 5:递归出口即是节点为空则返回。
public class Solution {
//返回的第一个指针,即为最小值,先定为null
public TreeNode head = null;
//中序遍历当前值的上一位,初值为最小值,先定为null
public TreeNode pre = null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
//中序递归,叶子为空则返回
return null;
//首先递归到最左最小值
Convert(pRootOfTree.left);
//找到最小值,初始化head与pre
if(pre == null){
head = pRootOfTree;
pre = pRootOfTree;
}
//当前节点与上一节点建立连接,将pre设置为当前值
else{
pre.right = pRootOfTree;
pRootOfTree.left = pre;
pre = pRootOfTree;
}
Convert(pRootOfTree.right);
return head;
}
}
复杂度分析
时间复杂度:O(n)O(n)O(n),其中nnn为二叉树节点数,中序遍历所有节点
空间复杂度:O(n)O(n)O(n),递归栈所需要的最大空间
方法二:非递归中序遍历(扩展思路)
知识点:栈
栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;元素出栈指的是从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
思路:
二叉树中序遍历除了递归方法,我们还可以尝试非递归解法,与常规的非递归中序遍历几乎相同,还是借助栈来辅助,只是增加了连接节点。
具体做法:
step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre),创建一个布尔型变量,标记是否是第一次到最左,因为第一次到最左就是链表头。
step 2:判断空树不能连接。
step 3:初始化一个栈辅助中序遍历。
step 4:依次将父节点加入栈中,直接进入二叉树最左端。
step 5:第一次进入最左,初始化head与pre,然后进入它的根节点开始连接。
step 6:最后将右子树加入栈中,栈中依次就弹出“左中右”的节点顺序,直到栈为空。
Java实现代码:
复制代码
import java.util.*;
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null)
return null;
//设置栈用于遍历
Stack<TreeNode> s = new Stack<TreeNode>();
TreeNode head = null;
TreeNode pre = null;
//确认第一个遍历到最左,即为首位
boolean isFirst = true;
while(pRootOfTree != null || !s.isEmpty()){
//直到没有左节点
while(pRootOfTree != null){
s.push(pRootOfTree);
pRootOfTree = pRootOfTree.left;
}
pRootOfTree = s.pop();
//最左元素即表头
if(isFirst){
head = pRootOfTree;
pre = head;
isFirst = false;
//当前节点与上一节点建立连接,将pre设置为当前值
}else{
pre.right = pRootOfTree;
pRootOfTree.left = pre;
pre = pRootOfTree;
}
pRootOfTree = pRootOfTree.right;
}
return head;
}
}
BM34 判断是不是二叉搜索树
方法一:递归(推荐使用)
思路:
二叉搜索树的特性就是中序遍历是递增序。既然是判断是否是二叉搜索树,那我们可以使用中序递归遍历。只要之前的节点是二叉树搜索树,那么如果当前的节点小于上一个节点值那么就可以向下判断。*只不过在过程中我们要求反退出。比如一个链表1->2->3->4,只要for循环遍历如果中间有不是递增的直接返回false即可。
if(root.val < pre)
return false;
具体做法:
step 1:首先递归到最左,初始化maxLeft与pre。
step 2:然后往后遍历整棵树,依次连接pre与当前节点,并更新pre。
step 3:左子树如果不是二叉搜索树返回false。
step 4:判断当前节点是不是小于前置节点,更新前置节点。
step 5:最后由右子树的后面节点决定。
import java.util.*;
public class Solution {
int pre = Integer.MIN_VALUE;
//中序遍历
public boolean isValidBST (TreeNode root) {
if (root == null)
return true;
//先进入左子树
if(!isValidBST(root.left))
return false;
if(root.val < pre)
return false;
//更新最值
pre = root.val;
//再进入右子树
return isValidBST(root.right);
}
}
复杂度分析:
时间复杂度:O(n),其中n为二叉树的节点数,最坏遍历整个二叉树所有节点
空间复杂度:O(n),最坏情况二叉树退化为链表,递归栈深度为n
方法二:非递归(扩展思路)
思路:
我们也可以利用栈来代替递归。如果一棵二叉树,对于每个根节点都优先访问左子树,那结果是什么?从根节点开始不断往左,第一个被访问的肯定是最左边的节点, 然后访问该节点的右子树,最后向上回到父问题。因为每次访问最左的元素不止对一整棵二叉树成立,而是对所有子问题都成立,因此循环的时候自然最开始都是遍历到最左:
//直到没有左节点
while(head != null){
s.push(head);
head = head.left;
}
然后访问,然后再进入右子树,我们可以用栈来实现回归父问题。
具体做法:
step 1:优先判断树是否为空,空树不遍历。
step 2:准备一个数组记录中序遍历的结果。
step 3:准备辅助栈,当二叉树节点为空了且栈中没有节点了,我们就停止访问。
step 4:从根节点开始,每次优先进入每棵的子树的最左边一个节点,我们将其不断加入栈中,用来保存父问题。
step 5:到达最左后,可以开始访问,如果它还有右节点,则将右边也加入栈中,之后右子树的访问也是优先到最左。
step 6:遍历数组,依次比较相邻两个元素是否为递增序。
import java.util.*;
public class Solution {
public boolean isValidBST(TreeNode root){
//设置栈用于遍历
Stack<TreeNode> s = new Stack<TreeNode>();
TreeNode head = root;
//记录中序遍历的数组
ArrayList<Integer> sort = new ArrayList<Integer>();
while(head != null || !s.isEmpty()){
//直到没有左节点
while(head != null){
s.push(head);
head = head.left;
}
head = s.pop();
//访问节点
sort.add(head.val);
//进入右边
head = head.right;
}
//遍历中序结果
for(int i = 1; i < sort.size(); i++){
//一旦有降序,则不是搜索树
if(sort.get(i - 1) > sort.get(i))
return false;
}
return true;
}
}
复杂度分析:
时间复杂度:O(n),其中n为二叉树的节点数,遍历整个二叉树后又遍历数组
空间复杂度:O(n),辅助栈及辅助数组的空间最坏为O(n)
BM37 二叉搜索树的最近公共祖先 描述
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
1.对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q,最近公共祖先LCA(T,p,q)表示一个节点x,满足x是p和q的祖先且x的深度尽可能大。在这里,一个节点也可以是它自己的祖先.
2.二叉搜索树是若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值
3.所有节点的值都是唯一的。
4.p、q 为不同节点且均存在于给定的二叉搜索树中。
数据范围:
3<=节点总数<=10000
0<=节点值<=10000
思路:一次遍历
具体做法:
step 1:首先检查空节点,空树没有公共祖先。
step 2:对于某个节点,比较与p、q的大小,若p、q在该节点两边说明这就是最近公共祖先。
step 3:如果p、q都在该节点的左边,则递归进入左子树。
step 4:如果p、q都在该节点的右边,则递归进入右子树。
import java.util.*;
public class Solution {
public int lowestCommonAncestor (TreeNode root, int p, int q) {
//空树找不到公共祖先
if(root == null)
return -1;
//pq在该节点两边说明这就是最近公共祖先
if((p >= root.val && q <= root.val) || (p <= root.val && q >= root.val))
return root.val;
//pq都在该节点的左边
else if(p <= root.val && q <= root.val)
//进入左子树
return lowestCommonAncestor(root.left, p, q);
//pq都在该节点的右边
else
//进入右子树
return lowestCommonAncestor(root.right, p, q);
}
}
时间复杂度:O(n),设二叉树共有n个节点,最坏情况递归遍历所有节点
空间复杂度:O(n),递归栈深度最坏为n
BM38 在二叉树中找到两个节点的最近公共祖先
递归
思路:
我们也可以讨论几种情况:
step 1:如果o1和o2中的任一个和root匹配,那么root就是最近公共祖先。
step 2:如果都不匹配,则分别递归左、右子树。
step 3:如果有一个节点出现在左子树,并且另一个节点出现在右子树,则root就是最近公共祖先.
step 4:如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。
step 5:继续递归左、右子树,直到遇到step1或者step3的情况。
import java.util.*;
public class Solution {
public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
//该子树没找到,返回-1
if(root == null)
return -1;
//该节点是其中某一个节点
if(root.val == o1 || root.val == o2)
return root.val;
//左子树寻找公共祖先
int left = lowestCommonAncestor(root.left, o1, o2);
//右子树寻找公共祖先
int right = lowestCommonAncestor(root.right, o1, o2);
//左子树为没找到,则在右子树中
if(left == -1)
return right;
//右子树没找到,则在左子树中
if(right == -1)
return left;
//否则是当前节点
return root.val;
}
}
3.5 难
BM39 序列化二叉树(难)
方法:前序遍历(推荐使用)
知识点:二叉树递归
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。
而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。
思路:
序列化即将二叉树的节点值取出,放入一个字符串中,我们可以按照前序遍历的思路,遍历二叉树每个节点,并将节点值存储在字符串中,我们用‘#’表示空节点,用‘!'表示节点与节点之间的分割。
反序列化即根据给定的字符串,将二叉树重建,因为字符串中的顺序是前序遍历,因此我们重建的时候也是前序遍历,即可还原。
具体做法:
step 1:优先处理序列化,首先空树直接返回“#”,然后调用SerializeFunction函数前序递归遍历二叉树。
SerializeFunction(root, res);
step 2:SerializeFunction函数负责前序递归,根据“根左右”的访问次序,优先访问根节点,遇到空节点在字符串中添加‘#’,遇到非空节点,添加相应节点数字和‘!’,然后依次递归进入左子树,右子树。
//根节点
str.append(root.val).append('!');
//左子树
SerializeFunction(root.left, str);
//右子树
SerializeFunction(root.right, str);
step 3:创建全局变量index表示序列中的下标(C++中直接指针完成)。
step 4:再处理反序列化,读入字符串,如果字符串直接为"#",就是空树,否则还是调用DeserializeFunction函数前序递归建树。
TreeNode res = DeserializeFunction(str);
step 5:DeserializeFunction函数负责前序递归构建树,遇到‘#’则是空节点,遇到数字则根据感叹号分割,将字符串转换为数字后加入新创建的节点中,依据“根左右”,创建完根节点,然后依次递归进入左子树、右子树创建新节点。
TreeNode root = new TreeNode(val);
......
//反序列化与序列化一致,都是前序
root.left = DeserializeFunction(str);
root.right = DeserializeFunction(str);
import java.util.*;
public class Solution {
//序列的下标
public int index = 0;
//处理序列化的功能函数(递归)
private void SerializeFunction(TreeNode root, StringBuilder str){
//如果节点为空,表示左子节点或右子节点为空,用#表示
if(root == null){
str.append('#');
return;
}
//根节点
str.append(root.val).append('!');
//左子树
SerializeFunction(root.left, str);
//右子树
SerializeFunction(root.right, str);
}
public String Serialize(TreeNode root) {
//处理空树
if(root == null)
return "#";
StringBuilder res = new StringBuilder();
SerializeFunction(root, res);
//把str转换成char
return res.toString();
}
//处理反序列化的功能函数(递归)
private TreeNode DeserializeFunction(String str){
//到达叶节点时,构建完毕,返回继续构建父节点
//空节点
if(str.charAt(index) == '#'){
index++;
return null;
}
//数字转换
int val = 0;
//遇到分隔符或者结尾
while(str.charAt(index) != '!' && index != str.length()){
val = val * 10 + ((str.charAt(index)) - '0');
index++;
}
TreeNode root = new TreeNode(val);
//序列到底了,构建完成
if(index == str.length())
return root;
else
index++;
//反序列化与序列化一致,都是前序
root.left = DeserializeFunction(str);
root.right = DeserializeFunction(str);
return root;
}
public TreeNode Deserialize(String str) {
//空序列对应空树
if(str == "#")
return null;
TreeNode res = DeserializeFunction(str);
return res;
}
}