剑指office算法题--二叉搜索树转换为双向链表

本文介绍如何将二叉搜索树转化为排序的双向链表,不创建新节点。方法包括:中序遍历后建立节点关系、线索化二叉树以及通过后序遍历优化。重点探讨了在遍历过程中直接调整节点指针,实现空间和时间效率的提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

思路

首先我们需要对题目进行解读,对于双向链表,我们可以理解为在单向链表拥有next指针的基础上再添加了一个指向前面节点的指针pre,如下所示:

next
pre
a
b

然后我们需要对二叉搜索树有一个了解。二叉搜索树是在二叉树的基础上其中节点的值满足cur.left.value<cur.value<cur.right.value这样一个关系,其中cur为当前节点的位置。如下图就是一个标准的二叉搜索树

6
4
9
3
5
8
10

这题的要求有两点

  • 不能创建新的节点
  • 转换后的双向链表是一个升序排序的

通过关键信息排序我们可以从二叉树的三种遍历思想出发,其中可以发现对于二叉搜索树进行中序遍历得到的结果正好满足升序排列。

对于二叉树的遍历我们可以这么理解X序遍历,这里的X就是说在遍历一个最基本的二叉树的时候根节点的遍历顺序

中序遍历:左子树>>根节点>>右子树
先序遍历:根节点>>左子树>>右子树
后序遍历:左子树>>右子树>>根节点

本题的解题方法有以下三种:

  • 先中序遍历二叉树,将输出的节点保存在一个ArrayList中,然后对ArrayList中的节点进行前后关系的创建
  • 利用二叉树线索化的过程思想,我们这里也可以提出一个全局变量pre,一开始给pre初始化为空,然后对二叉树进行中序遍历的时候,从叶子节点出发,只要pre不为空就创建关系,这样就可以生成双向链表了
  • 对上面方法的优化
方法一

我们需要定义一ArrayList,然后将中序遍历二叉树后的节点依次保存进ArrayList中,然后还需要一个函数将ArrayList中的独立的节点建立起前后的联系,最后输出ArrayList中的头节点即可。


import java.util.ArrayList
//定义一个二叉树的类
public class TreeNode {
   int val = 0;
   TreeNode left = null;
   TreeNode right = null;

   public TreeNode(int val) {
       this.val = val;

   }

public TreeNode Convert(TreeNode pRootOfTree) {
       if(pRootOfTree == null){
           return null;
       }
       ArrayList<TreeNode> list = new ArrayList<>();
       Convert(pRootOfTree, list);
       return Convert(list);

   }
   //中序遍历,在list中按遍历顺序保存
   public void Convert(TreeNode pRootOfTree, ArrayList<TreeNode> list){
       if(pRootOfTree.left != null){
           Convert(pRootOfTree.left, list);
       }

       list.add(pRootOfTree);

       if(pRootOfTree.right != null){
           Convert(pRootOfTree.right, list);
       }
   }
   //遍历list,修改指针
   public TreeNode Convert(ArrayList<TreeNode> list){
       for(int i = 0; i < list.size() - 1; i++){
           list.get(i).right = list.get(i + 1);
           list.get(i + 1).left = list.get(i);
       }
       return list.get(0);
   }

该方法较容易想到,是将题目的整体需求拆分为两部分分别进行求解的,由于多了一个链表关系建立的方法,所以时间复杂度会高一些
在这里插入图片描述

方法二

通过上面的法一我们可以知道线性二叉树的中序遍历输出的结果是满足我们题目要求的,但是还需要在对输出的ArrayList进行一次遍历建立节点的前后关联。我们想是否可以在中序遍历的时候就直接完成双向链表的前后节点关系的建立。

1.通过多层递归直接定位到二叉树的左子树的最左叶子节点位置,并且将节点指针preroot指向该位置

6
4
9
pre
root
  1. 此时的rootpre都不为空,让后移动pre指针,调整节点之间的关系
6
4
9
pre
root

3.重复步骤二的行为,

6
4
9
pre
root

最后输出root处的节点就可以了


public class Solution {
    TreeNode pre=null;
    TreeNode root=null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree==null)
            return null;
        Convert(pRootOfTree.left);
        if (root==null){
            root=pRootOfTree;
        }
        if (pre!= null){
            pRootOfTree.left=pre;
            pre.right=pRootOfTree;
        }
        pre=pRootOfTree;
        Convert(pRootOfTree.right);
        return root;
    }
}

该方法在法一的基础上进行优化,相对于法一,它没有开辟新的ArrayList去暂存节点,节省了空间;其二,他在进行中序遍历的时候就直接将节点的关系调整好了,不需要进一步的遍历ArrayList去调整节点关系,节省时间。
在这里插入图片描述

方法三

上面的方法二之所以需要用一个root暂存左子树的最左叶子节点的原因在于,我们的调整指针pre最后指向的是右子树的最右叶子节点,如果不用root暂存的话,会需要花费时间对形成的双向链表进行一次反向遍历。之所以会出现上面的情况是因为我们采用的中序遍历,如过我们采用后序遍历,那么pre指针一开始指向的就是右子树的最右叶子节点,随着遍历,最后直接指向的是左子树的最左叶子节点。

public class Solution {
    TreeNode pre=null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree==null)
            return null;
        Convert(pRootOfTree.right);
        if (pre!= null){
            pRootOfTree.right=pre;
            pre.left=pRootOfTree;
        }
        pre=pRootOfTree;
        Convert(pRootOfTree.left);
        return pre;
    }
}

改方法大体思路跟方法二一致,只不过通过改变二叉树的遍历方式去掉了root指针,看起来更简洁

总结

对于方法一是常规方法比较容易想到,就是要注意如果面试的时候考官问你有没有优化的时候,这个时候我们就可以从这个题目的优化角度出发。如果原先一个问题被拆分为两个相互独立的简单问题的时候,优化的思路就是在两个独立的方法中找到一个遍历的方法,然后看能不能将另外一个方法在一边遍历的时候就直接解决掉,这样就可以将两个问题合并为一个问题解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值