剑指offer-二叉搜索树与双向链表

题目描述:

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

问题分析:

  • 一般涉及到二叉树的问题,大部分都有个大套路,便是递归处理左右子树,以此缩小问题规模,此题也一样。
  • 因为是二叉搜索树,所以其中序遍历的结果应该是从小到大排序的。
  • 解法主要有两个大方向,一是用额外容器(方法一),二是不用额外容器(方法2),只是指针调整
  • 方法一:用额外容器收集该二叉树中序遍历的结果,然后依次遍历该容器中的每个节点,改变left与right指针,left指向该节点的前一个节点,right指向该节点的后一个节点,然后将每个节点串成一个双向链表。
  • 方法二:先递归遍历左子树,形成一个双向链表lList,假设process()函数实现该功能;再递归遍历右子树,形成一个双向链表rList,然后与当前节点相连。这是一个大思路,但是lList与当前节点相连时,必须知道该链表的尾节点。那么如何记录尾节点呢?
    • 2.1 :process()函数返回双向链表的头结点,当lList每一次与当前节点相连时,必须遍历该链表,找到该链的尾节点,然后才能连接。
      这种方法每一次都要遍历,比较费时。
    • 2.2: process()函数返回一个Node[]数组,Node[0]存着该链表头节点,Node[1]存放该链表尾节点,这样便于连接。
    • 2.3: process()函数返回双向链表的尾节点,但是该节点的right指针指向该双向链表的头结点,比Node[]数组方法节省空间,有点线索二叉树的意思。但最终还需要把指针改回来,改成null。

经验教训

  • 二叉树用递归解题的经典大套路
  • 二叉树可能会出现左子树或者右子树不存在的情况,一定要注意这样边界条件。具体细节见代码。

代码实现

  • 方法一
public static TreeNode convert1(TreeNode head) {
        if (head == null) {
            return null;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        //用队列来存储中序遍历的结果
        inOrderToQueue(head, queue);
        //依次出队,串成双向链表
        TreeNode start = queue.poll();
        //对于第一个节点,left一定不要忘了设为null
        start.left = null;
        start.right = null;
        TreeNode preNode = start;
        while (! queue.isEmpty()) {
            TreeNode curNode = queue.poll();
            //将当前节点与上一节点链接
            preNode.right = curNode;
            curNode.left = preNode;
            //并将当前节点right设为null,当然也可以最后统一置null
            curNode.right = null;
            //迭代
            preNode = curNode;
        }
        return start;
    }
    //将该二叉树中序遍历的结果存入队列中(大框架就是中序遍历,只不过将打印部分变成了入队)
    public static void inOrderToQueue(TreeNode head, Queue<TreeNode> queue) {
        if (head == null) {
            return;
        }
        //递归左子树
        inOrderToQueue(head.left, queue);
        //将当前节点入队
        queue.add(head);
        //递归右子树
        inOrderToQueue(head.right, queue);
    }
  • 方法2.2
public static TreeNode convert2(TreeNode head) {
        if (head == null ) {
            return null;
        }
        TreeNode[] res =  process(head);
        //返回链表头节点
        return res[0];
    }

    //process():将传入的搜索二叉树转成双向链表,并返回一个数组,0处为该链表头节点,1处为该链表尾节点
    public static TreeNode[] process(TreeNode head) {
        if (head == null) {
            return new TreeNode[] {null , null};
        }
        //递归左子树,将左子树转成双向链表
        TreeNode[] left = process(head.left);
        //递归右子树,将右子树转成双向链表
        TreeNode[] right = process(head.right);
        //start,end用作指示最终返回的链表的头指针与尾指针,起初都指向head
        TreeNode start = head;
        TreeNode end = head;

        //当前节点与左子树形成的左链相连
        head.left = left[1];//即使是空树,也是null
        if (left[1] != null) {//如果左子树不为空树
            left[1].right = head;
            start = left[0];
        }

        //当前节点与右子树形成的右链相连
        head.right = right[0];//即使是空树,也是null
        if (right[0] != null) {//如果右子树不为空树
            right[0].left = head;
            end = right[1];
        }
        //返回最终链表的首尾
        return new TreeNode[] {start, end};
    }
  • 方法2.3
public static TreeNode convert2(TreeNode head) {
        if (head == null) {
            return null;
        }
        TreeNode tail = process(head);
        //根据链表尾找头
        TreeNode start = tail.right;
        //将链表尾置为原样
        tail.right = null;
        return start;
    }
    //process():传入二叉搜索树,返回链表的尾节点,但是该链表的尾节点的right指向该链表头节点
    public static TreeNode process(TreeNode head) {
        if (head == null) {
            return null;
        }
        //递归左子树,将之串成双向链,返回链尾,注意链尾的right指向链头
        TreeNode lTail = process(head.left);
        //递归右子树,将之串成双向链,返回链尾,注意链尾的right指向链头
        TreeNode rTail = process(head.right);

        //先处理当前节点,因为可能左右子树都为空,所以一定预先设好left与right指针
        head.left = null;
        head.right = head;//这步非常重要
        //用headAll,tailAll记录链首与链尾
        TreeNode headAll = head;
        TreeNode tailAll = head;

        //如果左链(左子树)不为空,根节点与它相连
        if (lTail != null) {
            TreeNode lHead = lTail.right;
            head.left = lTail;
            lTail.right = head;
            //指向链首
            head.right = lHead;
            //更新链首
            headAll = lHead;
        }

        //如果右链(右子树)不为空,根节点与它相连
        if (rTail != null) {
            TreeNode rHead = rTail.right;
            head.right = rHead;
            rHead.left = head;
            //指向链首
            rTail.right = headAll;
            //更新链尾
            tailAll = rTail;
        }
        //返回最终链尾
        return tailAll;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值