今天在牛客网和力扣上带来了数据结构中二叉树的进阶练习题
1.二叉搜索树与双向链表———二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
2.二叉树遍历————二叉树遍历_牛客题霸_牛客网 (nowcoder.com)
3.二叉树的层序遍历————102. 二叉树的层序遍历 - 力扣(LeetCode)
4.二叉树的层序遍历————107. 二叉树的层序遍历 II - 力扣(LeetCode)
5.两个节点的最近公共祖先————236. 二叉树的最近公共祖先 - 力扣(LeetCode)
6.根据二叉树创建字符串————606. 根据二叉树创建字符串 - 力扣(LeetCode)
7.从前序和中序遍历构造二叉树————105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
8.从中序和后序遍历构造二叉树————106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
9.非递归二叉树的前序遍历————144. 二叉树的前序遍历 - 力扣(LeetCode)
1,二叉搜索树与双向链表
描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示
数据范围:输入二叉树的节点数 0≤n≤10000≤n≤1000,二叉树中每个节点的值 0≤val≤10000≤val≤1000
要求:空间复杂度O(1)O(1)(即在原树上操作),时间复杂度 O(n)O(n)注意:
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构4.你不用输出双向链表,程序会根据你的返回值自动打印输出
输入描述:
二叉树的根节点
返回值描述:
双向链表的其中一个头节点。
示例1
输入:
abc##de#g##f###复制输出:
c b e g d f a
思路:说实话这题我觉得挺难的,看解答才弄懂,我们要创建的链表的顺序就是二叉树中序遍历的顺序,所以我们用一个中序遍历的方法,在遍历过程中,记录我们前一个节点,让当前节点也前一个节点像链表一样连起来。
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
TreeNode a = inOrderTree(pRootOfTree);
return a;
}
public static TreeNode head=null;
public static TreeNode prev=null;
public static TreeNode inOrderTree(TreeNode root){
if(root==null){
return null;
}
inOrderTree(root.left);
if(prev==null){
head = root;
prev = root;
}
else{
prev.right = root;
root.left = prev;
prev = root;
}
inOrderTree(root.right);
return head;
}
}
我们来走一遍,
刚开始,head和prev都为空,我们进行中序遍历,从节点10开始,向左递归,到节点6,向左递归,到节点4,向左递归,到null,返回,我们此时判断prev是否为空,为空,我们把head和prev都设立为当前节点4,我们向右递归,为空,返回,4,全递归完毕,整体返回,到6,6的左节点递归完毕,进行prev判断,不为空,prev的right指向当前节点,当前节点的left指向前一个节点,向右递归,到8节点,8节点向左递归,为空,返回,判断prev,不为空,我们prev已经记录下前一个节点了,让当前节点的left指向前一个节点,前一个节点的right指向下一个当前节点,我们就完成了两个节点的链接,8,向右递归,遇到null,返回,6整体返回,到10,接下来重复这个过程。
注意1一定是对前一个节点进行操作,如果提前对后一个节点进行操作,那么就会丢失,二叉树就找不到节点了。
2,二叉树遍历
描述
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
输入描述:
输入包括1行字符串,长度不超过100。
输出描述:
可能有多组测试数据,对于每组数据, 输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。 每个输出结果占一行。
示例1
输入:
abc##de#g##f###复制输出:
c b e g d f a
思路:这道题我们要遍历字符串,因为是用先序遍历的方法,所以我们遇到字母的时候就创建节点,##的时候就为空,再对我们创建好的二叉树进行中序遍历。
import java.util.Scanner;
class TreeNode{
char val;
TreeNode left;
TreeNode right;
public TreeNode(char val){
this.val = val;
}
}
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while(in.hasNextLine()){
String str = in.nextLine();
TreeNode root = createTree(str);
inorderTree(root);
}
}
static int i = 0;
public static TreeNode createTree(String str){
TreeNode b = null;
char a = str.charAt(i);
if(a!='#'){
b = new TreeNode(a);
i++;
b.left=createTree(str);
b.right=createTree(str);
}else{
i++;
return null;
}
return b;
}
public static void inorderTree(TreeNode root){
if(root==null){
return ;
}
inorderTree(root.left);
System.out.print(root.val+" ");
inorderTree(root.right);
}
}
创建节点和中序遍历的方法就不说了,前几期都详细介绍了嗷,我们直接讲那个创建二叉树,还是刚才那样,一步一步来。
int i我们定义成为静态的因为我们递归的时候不希望他返回的时候没有进行操作,定义静态的就不受影响了,我们从字符串开始看,我们开始i为1,得到的第一个字符是a,a不等于#,我们创建节点A,i++,节点的左边我们开始递归,字符不为#,创建节点,i++,这个节点此时已经与A的左边连上了,我们继续向左递归,字符不为#,i++,创建节点c,与b的左边连上,再向左递归,遇到null,i++,返回null,此时来到c的左边,c向右递归,遇到井号,i++,返回null,返回到b,b向右递归............一直下去。
3,二叉树的层序遍历
给你二叉树的根节点
root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]示例 2:
输入:root = [1] 输出:[[1]]示例 3:
输入:root = [] 输出:[]
思路:这题不难的,我们之前详细做过二叉树的层序遍历,我们只要用一个引用,和一个队列就OK了,但是这题不一样,他想让我们返回一个类似二维数组的链表
我们只需要之前层序遍历打印元素的时候,提前算出我们要放在每一层的元素个数,再把他们都放到小链表中,最后再放到大链表中。
class Solution {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
List<List<Integer>> list1 = new LinkedList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null){
return list1;
}
TreeNode cur = root;
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> list2 = new LinkedList<>();
int sz = queue.size();
for(int i=1;i<=sz;i++){
cur = queue.poll();
list2.add(cur.val);
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
list1.add(list2);
}
return list1;
}
}
4,二叉树的层序遍历2
给你二叉树的根节点
root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)示例 1:
输入:root = [3,9,20,null,null,15,7] 输出:[[15,7],[9,20],[3]]示例 2:
输入:root = [1] 输出:[[1]]示例 3:
输入:root = [] 输出:[]
思路:跟刚才的题基本一致,只需要改一个地方,我们已经实现了刚才的二维链表了,我们在每次实现小链表的思路不变,只需要在实现大链表的时候,实现的不是尾插,而是头插即可。
class Solution {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> list1 = new ArrayList<>();
public List<List<Integer>> levelOrderBottom(TreeNode root) {
if(root==null){
return list1;
}
TreeNode cur = null;
queue.offer(root);
while(!queue.isEmpty()){
int sz = queue.size();
List<Integer> list2 = new ArrayList<>();
while(sz>0){
cur = queue.poll();
list2.add(cur.val);
sz--;
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
list1.add(0,list2);
}
return list1;
}
}
5,两个节点的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点5
和节点1
的最近公共祖先是节点3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出:5 解释:节点5
和节点4
的最近公共祖先是节点5 。
因为根据定义最近公共祖先节点可以为节点本身。示例 3:
输入:root = [1,2], p = 1, q = 2 输出:1
这道题我们可以使用两个方法,第一个就是传统的二叉树遍历,第二个方法就是找他们的交点,
第一种方法,思路:我们去遍历这个二叉树,从左树开始完了右树,我们可以分为4种情况。
p或者q就在树的根节点,此时根节点直接就是我们要找的祖先节点。
第二种情况就是p和q在左树和右树的两边,当二叉树从左到右全部递归完毕的时候,我们走到节点,此时判断leftNode和rightNode是否为空,如果不为空,说明当前节点已经是p,q节点的最近公共祖先了,我们只需一直返回它即可,这种情况,一定是根节点,
那这个只能判断根节点,那么这个图怎么弄呢
我们在5节点找到最近公共祖先,返回5节点,3节点左边接收到5节点,但是右边为null,无法输出,所以我们在最后两行判断了这种特殊情况,如果只有左节点或者只有有节点成功获得祖先节点的返回值,我们就输出获得的这个节点,因为我们知道,如果3的左边没有获得q节点,那么它一定是在左边且p节点的下边,因为题目说了一定有p,q节点的。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null){
return root;
}
if(root.val==p.val || root.val==q.val){
return root;
}
TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
if(leftNode!=null && rightNode!=null){
return root;
}
if(leftNode!=null){
return leftNode;
}
return rightNode;
}
}
第二种方法,我们想象成两个链表相交的思路
我们遍历二叉树把p和q的长度放到对应的栈中
再让长的那个栈出一个元素,之后两个栈一起出最后剩的一个元素就是我们的最近公共祖先。
class Solution {
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null){
return null;
}
getpath(root,p,stack1);
getpath(root,q,stack2);
if(stack1.size()>stack2.size()){
Stack<TreeNode> tmp = new Stack<>();
tmp = stack1;
stack1 = stack2;
stack2 = tmp;
}
while(stack2.size()-stack1.size()>0){
stack2.pop();
}
while(stack1.peek().val != stack2.peek().val){
stack1.pop();
stack2.pop();
}
return stack2.pop();
}
public boolean getpath(TreeNode root,TreeNode p,Stack<TreeNode> stack){
if(root==null){
return false;
}
stack.push(root);
if(root.val==p.val){
return true;
}
boolean a = getpath(root.left,p,stack);
if(a==true){
return true;
}
boolean b = getpath(root.right,p,stack);
if(b==true){
return true;
}
stack.pop();
return false;
}
}
我们看着这个getpath代码走几次,就拿题目中的p=5,q=1, 和p=5,q=4。
我们找q节点
我们从节点3开始将节点3放入stack1中,向左递归,到5节点,将5放到stack1中,向左递归,到节点6,将节点6放到stack1中,向左递归,返回null,向右递归,返回null,将节点6移出stack中,返回5节点,5节点向右递归,到2节点,将2节点放入stack1中,向左递归,遇到节点7,将节点7放到stack中,向左递归,遇到null返回,向右递归,遇到null,返回,将7移出stack,返回到节点2,向右递归,遇到节点4,找到q节点了,返回true,到2节点,返回true,到5,返回true,到3的左边,返回true;找到q节点,p节点同理,当q=1的时候,左边没有一个true,全部都被移出了。
6,根据二叉树创建字符串
给你二叉树的根节点
root
,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。空节点使用一对空括号对
"()"
表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。示例 1:
输入:root = [1,2,3,4] 输出:"1(2(4))(3)" 解释:初步转化后得到 "1(2(4)())(3()())" ,但省略所有不必要的空括号对后,字符串应该是"1(2(4))(3)" 。示例 2:
输入:root = [1,2,3,null,4] 输出:"1(2()(4))(3)" 解释:和第一个示例类似,但是无法省略第一个空括号对,否则会破坏输入与输出一一映射的关系。
思路:这道题就不是很难了,我们观察每个输入输出的例子,对于每个节点有几个子节点就会在后面出现(对应节点),null也为(),所以的父亲节点都会在()的前面。
class Solution {
public static StringBuilder sb;
public String tree2str(TreeNode root) {
sb = new StringBuilder("");
createString(root);
String str = sb.toString();
return str;
}
public void createString(TreeNode root){
if(root==null){
return;
}
sb.append(root.val);
if(root.left!=null){
sb.append("(");
createString(root.left);
sb.append(")");
}else{
if(root.right==null){
return;
}
else{
sb.append("(");
sb.append(")");
}
}
if(root.right!=null){
sb.append("(");
createString(root.right);
sb.append(")");
}else{
return;
}
}
}
这道题我们用子问题思路,关注每个节点,每次递归时,我们都先把当前节点拼接到字符串上,我们使用StringBuilder来拼接,而不是用string去+,因为在使用String+的时候会创建很多的对象,浪费很多的内存和时间,我们拼接好后判断当前节点左右子节点的情况,在对其进行操作。
7,前序遍历与中序遍历构造二叉树
给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 输出: [3,9,20,null,null,15,7]示例 2:
输入: preorder = [-1], inorder = [-1] 输出: [-1]
思路:
我们是怎么通过先序遍历和中序遍历构建字符串的呢,我们先看前序遍历,因为前序遍历,每次都是先遇到节点,再向左,向右遍历。所以我们创建二叉树的时候也是按照前序遍历的顺序构建节点,接下来就要考虑节点的左右了,我们这时使用中序遍历,把中序遍历的头记为begin,尾记为end,我们先找跟节点,前序遍历的头,在中序遍历中找到前序遍历的头,就是我们的根节点,我们把它的下标记为index。
这时我们再分两边构建两树,左边的begin不变,end变味index-1,右边end不变,begin=index+1
继续递归。
此时左树的end已经小于begin,左树的递归就停止了,右树继续
右树的左子树递归完毕,begin>end,递归右子,
递归全部完毕。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTreeChild(preorder,inorder,0,inorder.length-1);
}
int i = 0;
public TreeNode buildTreeChild(int[] preorder, int[] inorder, int begin, int end){
if(begin>end){
return null;
}
TreeNode root = new TreeNode(preorder[i]);
int index = FindIndex(preorder,inorder,begin,end);
i++;
root.left = buildTreeChild(preorder,inorder,begin,index-1);
root.right = buildTreeChild(preorder,inorder,index+1,end);
return root;
}
public int FindIndex(int[] preorder,int[] inorder, int begin,int end){
for(int j = begin;j<=end;j++){
if(preorder[i]==inorder[j]){
return j;
}
}
return -1;
}
}
8,从中序与后序遍历构建二叉树
给定两个整数数组
inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。示例 1:
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3] 输出:[3,9,20,null,null,15,7]示例 2:
输入:inorder = [-1], postorder = [-1] 输出:[-1]
思路和刚才前序遍历加中序遍历构建二叉树一样。但是我们这次是从后序遍历最后一个下边往前遍历创建节点, 在每次递归时我们都是先创建右树,在左树。
class Solution {
public int i;
public TreeNode buildTree(int[] inorder, int[] postorder) {
i = postorder.length-1;
return buildTreeChild(inorder,postorder,0,inorder.length-1);
}
public TreeNode buildTreeChild(int[] inorder,int[] postorder,int begin,int end){
if(begin>end){
return null;
}
TreeNode root = new TreeNode(postorder[i]);
int index = findIndex(inorder,postorder,begin,end);
i--;
root.right = buildTreeChild(inorder,postorder,index+1,end);
root.left = buildTreeChild(inorder,postorder,begin,index-1);
return root;
}
public int findIndex(int[] inorder,int[] postorder,int begin,int end){
for(int j=begin;j<=end;j++){
if(postorder[i]==inorder[j]){
return j;
}
}
return -1;
}
}
9,非递归的二叉树的前序遍历
给你二叉树的根节点
root
,返回它节点值的 前序 遍历。示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
解释:
示例 2:
输入:root = [1,2,3,4,5,null,8,null,null,6,7,9]
输出:[1,2,4,5,6,7,3,8,9]
解释:
示例 3:
输入:root = []
输出:[]
示例 4:
输入:root = [1]
输出:[1]
这题我们之前做过我们使用最基础的递归,我们可不可以用迭代做呢
既然是官方给的,那必须做了。
我们之前用队列实现过层序遍历,其实原理都一样,我们这次使用栈来完成前序遍历。
看这个图,我们把一放到栈中,再把栈中的第一个元素pop到cur中。
把cur右边的元素先放到栈,再放cur左边的栈。再出栈中的第一个元素,如此往复。
class Solution {
Stack<TreeNode> stack1 = new Stack<>();
List<Integer> list = new LinkedList<>();
public List<Integer> preorderTraversal(TreeNode root) {
if(root==null){
return list;
}
TreeNode cur = null;
stack1.push(root);
while(!stack1.isEmpty()){
cur = stack1.pop();
if(cur==null){
continue;
}
else{
list.add(cur.val);
stack1.push(cur.right);
stack1.push(cur.left);
}
}
return list;
}
}