二叉树——(四)进阶面试题

本文介绍了多种基于遍历序列还原和操作二叉树的方法,包括根据先序和中序、后序遍历结果重建二叉树,以及将二叉树转换为字符串、构建完全二叉树、转换为双向链表等。这些题目考察了递归和二叉树性质的理解,以及如何有效地利用遍历序列来构建和操作二叉树结构。
摘要由CSDN通过智能技术生成

目录

1.根据先序和中序遍历结果还原二叉树

2.根据中序和后序遍历结果还原二叉树

3.从先序遍历还原二叉树 

4.从先序遍历输出为中序遍历结果

5.二叉树的最近公共祖先

6.根据二叉树创建字符串

7.递增顺序搜索树

8.二叉树的完全性检验

9.二叉搜索树转双向链表 


1.根据先序和中序遍历结果还原二叉树

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode) (leetcode-cn.com)

重建二叉树_牛客题霸_牛客网 (nowcoder.com)

 遍历到的前序作为根节点,通过找中序遍历中该节点的位置确定左右子树的范围;

不断遍历前序节点进行递归,不断缩小范围,递归的过程实则就是在还原连接左右子树

class Solution {
    int index;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeHelper(preorder,inorder,0,preorder.length);
    }
//    借助中序遍历在【left,right]范围内找左右子树,还原二叉树并返回根节点,
    public TreeNode buildTreeHelper(int[] preorder,int[] inorder,int left,int right){
//        边界
        if(left > right){
            return null;
        }
        if(index == preorder.length){
            return null;
        }
//        依据前序遍历创建根节点
        TreeNode root = new TreeNode(preorder[index]);
        index ++;
//        找到该根节点在中序遍历中的位置
        int position = find(root.val,inorder);
//        递归左子树
        root.left = buildTreeHelper(preorder,inorder,left,position - 1);
//        递归右子树
        root.right = buildTreeHelper(preorder,inorder,position + 1,right);
        return root;
    }
 
 
    private int find(int value, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            if(inorder[i] == value){
                return i;
            }
        }
        return -1;
        
 
 
    }
}

2.根据中序和后序遍历结果还原二叉树

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode) (leetcode-cn.com)

下面这种做法跟上面第一题整体思路一致,稍稍不同,这种做法更具普适性,对于根据前中还原,根据后中还原,都可套用这个模板 

下面这种做法,是对两个数组都在进行范围的缩小与确认

思路:

从后序遍历最后一个节点作为根节点开始,不断向前遍历,然后在中序遍历里确定该节点位置,由此确认中序遍历中左右子树的位置,再根据左右子树的个数,确定后序遍历结果中的左右子树的范围,递归左右即可。

public class Leetcode_T106_in_post {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int n1 = inorder.length;
        int n2 = postorder.length;
        if(n1 == 0 || n2 == 0 || n1 != n2){
            return null;
        }
        return buildHelper(inorder,postorder,0,inorder.length - 1,0,postorder.length - 1);
    }

    private TreeNode buildHelper(int[] inorder, int[] postorder, int inorderStart, int inorderEnd, int postorderStart, int postorderEnd) {
        if(inorderStart > inorderEnd || postorderStart > postorderEnd){
            return null;
        }
        TreeNode newRoot = new TreeNode(postorder[postorderEnd]);
        //        在中序遍历里找后序根的位置
        for (int j = inorderStart; j <= inorderEnd; j++) {
            if(inorder[j] == postorder[postorderEnd]){
                int mid = j;
//                关键是找清左右子树在中序、后序遍历中的范围!!!画图清楚一点
                newRoot.left = buildHelper(inorder,postorder,inorderStart,mid - 1,postorderStart,postorderStart + mid - inorderStart - 1);
                newRoot.right = buildHelper(inorder,postorder,mid + 1,inorderEnd,postorderEnd - 1 - (inorderEnd - mid ) + 1 ,postorderEnd - 1);
                break;
            }
        }
// 返回根节点
        return newRoot;
    }

3.从先序遍历还原二叉树 

1028. 从先序遍历还原二叉树 - 力扣(LeetCode) (leetcode-cn.com)

同样是根据先序遍历结果还原二叉树,这道题较之第四题略显难一点,因为第四题借助递归即可实现,而这道题,递归不再适用,而要根据给出的每个节点的深度来构建二叉树

  • 记录每个节点的值
  • 记录每个节点的深度
  • 借助栈完成还原:如果栈内元素个数与待入栈元素的深度一致,说明,栈顶元素即为待入栈元素的父节点,如果不一致,说明待入栈元素是某个节点的右节点,所以栈内元素出栈直至找到父节点,并连接该关系
    public TreeNode recoverFromPreorder(String traversal) {
//        index是索引下标,遍历后移
        int index = 0;
        Deque<TreeNode> stack = new LinkedList<>();
        while (index < traversal.length()) {
            //        当遇到深度表示符
            //        depth记录每次遇到的节点的深度
            int depth = 0;
            while (traversal.charAt(index) == '-') {
                depth++;
                index++;
            }
            //        val记录每次节点的值
            int val = 0;
//        当遇到数字
            while (index < traversal.length() && Character.isDigit(traversal.charAt(index))) {
                val = val * 10 + (traversal.charAt(index) - '0');
                index++;
            }

//        此时,节点值为val,深度为depth
            TreeNode node = new TreeNode(val);

//            如果节点深度与栈内元素个数一样,则连接栈顶与左节点,并入栈(记得先判空)
            if (depth == stack.size()) {
//                如果栈不为空,则连接左节点,如果栈为空,直接入栈
                if (!stack.isEmpty()) {
                    stack.peek().left = node;
                }
                stack.push(node);
            } else {
//             否则,说明遇到深度小的节点了,说明是栈内某节点的右节点
                while (stack.size() != depth) {
                    stack.pop();
                }
//                出栈到这里,说明栈顶是父节点
                stack.peek().right = node;
                stack.push(node);
            }
        }
//        最后返回根节点
        while (stack.size() > 1) {
            stack.pop();
        }
        return stack.peek();
    }

4.从先序遍历输出为中序遍历结果

二叉树遍历_牛客题霸_牛客网 (nowcoder.com)

关键点是如何根据给出的先序遍历结果来还原二叉树:

先序遍历是根左右的顺序,我们还可以借助递归来实现:当遇到#说明是空节点,返回Null,然后按照根左右的顺序递归构建

import java.util.*;
public class Main{
    private static int index = 0;
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        TreeNode root = buildTree(s);
        midOrder(root);    
    }
    
//     构建二叉树
    public static TreeNode buildTree(String s){
        if(s.charAt(index) == '#'){
           return null;
           }
//            如果不为null,则开始建节点
        TreeNode node = new TreeNode(s.charAt(index));
        index ++;
        node.left = buildTree(s);
        index ++;
        node.right = buildTree(s);
        return node;              
    }
//     中序遍历输出
    public static void midOrder(TreeNode root){
        if(root == null){
            return;
        }
//         左
        midOrder(root.left);
//         根
        System.out.print(root.value + " ");
//         右
        midOrder(root.right);
    }
    
}
class TreeNode{
    char value;
    TreeNode left;
    TreeNode right;
    public TreeNode(char value){
        this.value = value;
    }
}

5.二叉树的最近公共祖先

​​​​​​236. 二叉树的最近公共祖先 - 力扣(LeetCode) (leetcode-cn.com)

思路:

在最近公共节点处,有这么几种可能:

(1)p、q恰好在公共节点的一左一右

(2)最近公共节点即为p或q之一,另一个节点位于左或右

故而,我们可以在查找某节点的递归方法中顺便记录最近公共节点

这样定义:如果在左子树能找到p或者q,记作1,否则记作0;同样,如果能在右子树找到p或者q,再记为1,否则为0;同时,判断该根节点是否就是p或q,如果是也记作1,不是记作0,如果存在这三个数之和=2,说明该节点出发能找到p和q,最后,只需返回最近的即可。

class Solution {
    private TreeNode ret;

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        find(root, p, q);
        return ret;
    }

    //    在以node为根结点的子树中能否找到结点p或者q
    public boolean find(TreeNode node, TreeNode p, TreeNode q) {
        if (node == null) {
            return false;
        }
//        递归查找左子树
        int left = find(node.left, p, q) ? 1 : 0;
//        递归查找右子树
        int right = find(node.right, p, q) ? 1 : 0;
//        看是否根节点就是p或者q
        int mid = (node == p || node == q) ? 1 : 0;
//        如果=2,说明p q一个在左子树一个在右子树或者一个是根节点另一个是左子树或右子树
        if (left + right + mid == 2) {
            ret = node;
        }
        return (left + right + mid) > 0;
        
    }
}

 

6.根据二叉树创建字符串

606. 根据二叉树创建字符串 - 力扣(LeetCode) (leetcode-cn.com)

关键是对括号的拼接:

先拼接根节点和左括号——》如果有左节点,接着正常拼接左节点(即递归左),递归左结束,加右括号——》如果没有左节点,则需要先判断是否有右节点——》如果有右节点,则空的左需要用一对括号代替,然后再去递归右子树——》如果没有右节点,说明左右节点都没有,无需再管

//你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。
//空节点则用一对空括号 "()" 表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
public class Leetcode_T606_Tree2Str {
    StringBuilder s = new StringBuilder();

    public String tree2str(TreeNode root) {
        if (root == null) {
            return "";
        }
//        先根结点
        s.append(root.val);
//        当左子树不为空,正常拼接
        if (root.left != null) {
            s.append("(");
//            递归左子树
            tree2str(root.left);
            s.append(")");
        } else {
//            当左子树为空,仅当右子树不为空时才添加()
            if (root.right != null) {
                s.append("()");
            }
        }
//        最后处理右子树
        if (root.right != null) {
            s.append("(");
            tree2str(root.right);
            s.append(")");
        }
        return s.toString();
    }
}

 

7.递增顺序搜索树

897. 递增顺序搜索树 - 力扣(LeetCode) (leetcode-cn.com)

按左根右的顺序重新连接二叉树 

  • 递归左,找到左部分的头节点和尾节点,头节点用于方法的返回,尾节点则去和根节点进行连接;
  • 再递归右,将根节点与右部分连接
class Solution {
    public TreeNode increasingBST(TreeNode root) {
        if(root == null){
            return null;
        }
        // 先左,记录左子树排列为递增顺序搜索树的头节点
        TreeNode leftHead = increasingBST(root.left);
        // 因为左部分接着要跟根节点连接,所以,我们需要找到左部分的尾节点
        TreeNode leftTail = leftHead;
        while(leftTail != null && leftTail.right != null){
            leftTail = leftTail.right;
        }
        // 左部分要跟根节点连接,注意判空,有可能左部分为空
        if(leftTail != null){
            // 连接新线
            leftTail.right = root;
            // 断开之前的线,否则会形成闭环
            root.left = null;
        }
        // 最后处理根节点与右部分的头节点的连接
        TreeNode rightHead = increasingBST(root.right);
        if(rightHead != null){
            // 连线再断线
            root.right = rightHead;
            rightHead.left = null;
        }
        return leftHead == null ? root : leftHead;
    }
}

 

8.二叉树的完全性检验

958. 二叉树的完全性检验 - 力扣(LeetCode) (leetcode-cn.com)

这道题其实就是让你判断给定的二叉树是否是完全二叉树

所以,让我们来分析下完全二叉树的特点,是的,可以发现,对于一棵完全二叉树,按层序遍历,当碰到第一个没有左孩子的节点时,必然,从该节点开始之后的所有节点一定必须得是叶子节点,否则,必然不满足完全二叉树的条件

从这个条件出发,我们在代码中可以引入一个boolean变量作为标志位,来判断是否到了叶子节点的交界点。状态一为默认状态,表明应当是左右孩子都有,当遇到第一个没有左孩子的节点时,切换为状态二,状态二以后的节点必须既无左孩子也无右孩子 

class Solution {
    public boolean isCompleteTree(TreeNode root) {
        boolean state = false;
        if(root == null){
            return true;
        }
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        // 层序遍历
        while(!queue.isEmpty()){
            TreeNode cur = queue.poll();
            // 如果是状态一,则只要有左右孩子为空的就不是完全二叉树
            if(!state){
                // 如果左右子树都不为空,则正常继续层序遍历
                if(cur.left != null && cur.right != null){
                    queue.offer(cur.left);
                    queue.offer(cur.right);
                }else if(cur.right != null){
                    // 如果左子树为空,右子树不为空,直接返回false
                    return false;
                }else if(cur.left != null){
                    // 如果左子树不为空,右子树为空,则调整状态为状态二
                    state = true;
                    queue.offer(cur.left);
                }else{
                    // 最后的情况,左右孩子都为空,说明碰到了第一个叶子节点,也调整状态为状态二
                    state = true;
                }
            }else{
                // 否则是状态二,则必须左右孩子全部为空才是完全二叉树
                if(cur.left != null || cur.right != null){
                    return false;
                }
            }
        }
        // 如果遍历完,都未返回false,说明是完全二叉树
        return true;
    }
}

 

9.二叉搜索树转双向链表 

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)

其实这道题和上面的第七道题基本一模一样,思路一致,只是连线稍微有点区别而已。

思路还是:

找到左的头节点,尾节点;

将左的尾同根节点连接;

再递归找到右的头节点;

将根节点与右的头节点连接;

最后返回左的头节点(如果左为空,返回根节点)

public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
      if(pRootOfTree == null){
          return null;
      }  
//         先递归左
//         找到左部分的头节点和尾节点
        TreeNode leftHead = Convert(pRootOfTree.left);
        TreeNode leftTail = leftHead;
        while(leftTail != null && leftTail.right != null){
            leftTail = leftTail.right;
        }
//         将左与根连接
        if(leftTail != null){
            leftTail.right = pRootOfTree;
            pRootOfTree.left = leftTail;
        }
//         再递归右子树
        TreeNode rightHead = Convert(pRootOfTree.right);
//         将根节点与右部分连接
        if(rightHead != null){
            pRootOfTree.right = rightHead;
            rightHead.left = pRootOfTree;
        }
//         最后返回头节点
        return leftHead == null ? pRootOfTree : leftHead;
    }
}

呼~二叉树还是得再多练练

 

  • 15
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨笨在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值