题解 | #二叉搜索树与双向链表#

二叉搜索树与双向链表

http://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5

题目的主要信息:
  • 将二叉搜索树转化成递增序的双向链表
  • 不能添加新的节点,要在原节点基础上添加链表链接
  • 返回链表中的第一个节点的指针
  • 二叉树节点的左右指针看成双向链表的前后指针
举一反三:

学习完本题的思路你可以解决如下题目:

BM34. 判断是不是二叉搜索树

BM37. 二叉搜索树的最近公共祖先

方法一:递归中序遍历(推荐使用)

知识点1:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

知识点2:二叉搜索树

二叉搜索树是一种特殊的二叉树,它的每个节点值大于它的左子节点,且大于全部左子树的节点值,小于它右子节点,且小于全部右子树的节点值。因此二叉搜索树一定程度上算是一种排序结构。

思路:

二叉搜索树最左端的元素一定最小,最右端的元素一定最大,符合“左中右”的特性,因此二叉搜索树的中序遍历就是一个递增序列,我们只要对它中序遍历就可以组装称为递增双向链表。

具体做法:

  • step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre)。
  • step 2:首先递归到最左,初始化head与pre。
  • step 3:然后处理中间根节点,依次连接pre与当前节点,连接后更新pre为当前节点。
  • step 4:最后递归进入右子树,继续处理。
  • step 5:递归出口即是节点为空则返回。

图示:

图片说明

Java实现代码:

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;
    }
}

C++实现代码:

class Solution {
public:
    //返回的第一个指针,即为最小值,先定为NULL
    TreeNode* head = NULL;  
    //中序遍历当前值的上一位,初值为最小值,先定为NULL
    TreeNode* pre = NULL;   
    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;
    }
};

Python实现代码

class Solution:
    head = None
    pre = None
    def Convert(self , pRootOfTree ):
        if not pRootOfTree:
            # 中序递归,叶子为空则返回
            return None       
        # 首先递归到最左最小值
        self.Convert(pRootOfTree.left) 
        # 找到最小值,初始化head与pre
        if not self.pre:       
            self.head = pRootOfTree
            self.pre = pRootOfTree
        # 当前节点与上一节点建立连接,将pre设置为当前值
        else:       
            self.pre.right = pRootOfTree
            pRootOfTree.left = self.pre
            self.pre = pRootOfTree
        self.Convert(pRootOfTree.right)
        return self.head

复杂度分析

  • 时间复杂度:O(n)O(n),其中nn为二叉树节点数,中序遍历所有节点
  • 空间复杂度: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;
    }
}

C++实现代码:

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if (pRootOfTree == NULL) {
            return NULL;
        }
        //设置栈用于遍历
        stack<TreeNode*> s; 
        TreeNode* head = NULL;
        TreeNode* pre = NULL;
        //确认第一个遍历到最左,即为首位
        bool isFirst = true; 
        while(pRootOfTree != NULL || !s.empty()){
            //直到没有左节点
            while(pRootOfTree != NULL){   
                s.push(pRootOfTree);
                pRootOfTree = pRootOfTree->left;
            }
            pRootOfTree = s.top();
            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;
    }
};

Python实现代码

class Solution:
    def Convert(self , pRootOfTree ):
        if not pRootOfTree:
            return None
        # 设置栈用于遍历
        s = [] 
        head = None
        pre = None
        # 确认第一个遍历到最左,即为首位
        isFirst = True  
        while pRootOfTree or s: 
            # 直到没有左节点
            while pRootOfTree:    
                s.append(pRootOfTree)
                pRootOfTree = pRootOfTree.left
            pRootOfTree = s[-1]
            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

复杂度分析:

  • 时间复杂度:O(n)O(n),其中nn为二叉树节点数,中序遍历二叉树所有节点
  • 空间复杂度:O(n)O(n),栈s最大空间为为O(n)O(n)
全部评论
题目要求空间复杂度O(1),你给的2个方法都是O(n)。无语,给不出O(1)的答案,但是给O(1)的要求...
4 回复 分享
发布于 01-13 20:40 上海
你看看你的答案空间复杂度是O(1)吗
3 回复 分享
发布于 2023-10-02 15:51 美国
牛逼
点赞 回复 分享
发布于 2022-08-01 23:29
niu
点赞 回复 分享
发布于 2022-12-08 22:20 浙江
这递归是真的精彩啊,叹为观止。
点赞 回复 分享
发布于 2023-03-17 02:57 江苏
这才是符和人类思维的题解啊,,,,GPT给的都些什么魔鬼代码,
点赞 回复 分享
发布于 2023-04-14 15:01 四川
这就是中序遍历,无论是递归还是非递归的空间复杂度只能是O(N)。不如直接拿到一个Vector修改的舒服
点赞 回复 分享
发布于 10-27 22:41 浙江

相关推荐

Noel_:中石油是这样的 哥们侥幸混进免笔试名单 一看给我吓尿了
点赞 评论 收藏
分享
11-09 11:01
济南大学 Java
Java抽象带篮子:外卖项目真得美化一下,可以看看我的详细的外卖话术帖子
点赞 评论 收藏
分享
37 14 评论
分享
牛客网
牛客企业服务