面试题36-二叉搜索树与双向链表

题目:

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

解题思路

首先,让我们来回顾一下搜索二叉树的结构一些相关特点:
(1)在二叉搜索树中,每个节点都有两个分别指向其左、右子树的指针;
(2)左子树节点的值总是小于父节点的值,右子树节点的值总是大于父节点的值。
该结构特点可以类比到双向链表中:
在双向链表中,每个节点也有两个指针,它们分别指向前一个节点和后一个节点。

所以这两种数据结构的节点是一致,二叉搜索树之所以为二叉搜索树,双向链表之所以为双向链表,只是因为两个指针的指向不同而已,通过改变其指针的指向来实现是完全可能的。

如下图所示:
在这里插入图片描述
具体实现步骤:
原先指向左子节点的指针调整为链表中指向前一个节点的指针,原先指向右子节点的指针调整为链表中指向下一个节点的指针。
具体转换过程:按照中序遍历的方法遍历二叉搜索树,可以将该二叉树分为三个部分:根节点、左子树和右子树,当遍历节点值为4的节点时,将它分为以2为节点的左子树和以6为节点的右子树,并将4的左指针指向值为3的节点,值为3的节点的右指针指向值为4的节点,因为采用的是中序遍历,所以当遍历到根节点的时候,它的左子树已经遍历结束了,所以要对所有的子树采用递归的执行上述操作。

C++实现

struct BinaryNode
{

	int m_nvalue;
	BinaryNode*m_pLeft;
	BinaryNode*m_pRight;
	BinaryNode() : m_nvalue(0), m_pLeft(nullptr), m_pRight(nullptr) {}
	BinaryNode(int x) : m_nvalue(x), m_pLeft(nullptr), m_pRight(nullptr) {}
	
};
class Solution{
public:
	BinaryNode *Convert(BinaryNode*pRootOfTree)
	{
		BinaryNode *pLastNodeInList=nullptr;
		ConvertNode(pRootOfTree,&pLastNodeInList);
		//pLastNodeInList指向双向链表的尾节点
		//我们需要返回头节点
		BinaryNode *pHeadOfList=pLastNodeInList;
		while(pHeadOfList&&pHeadOfList->m_pLeft)
			pHeadOfList=pHeadOfList->m_pLeft;
		return pHeadOfList;
	}
	void ConvertNode(BinaryNode *pNode,BinaryNode ** pLastNodeInList)
	{

		if(pNode==nullptr)
			return ;
		BinaryNode *pCurrent=pNode;
		if(pCurrent->m_pLeft)
			ConvertNode(pCurrent->m_pLeft,pLastNodeInList);
		pCurrent->m_pLeft=*pLastNodeInList;
		if(*pLastNodeInList)
			(*pLastNodeInList)->m_pRight=pCurrent;
		*pLastNodeInList=pCurrent;
		if(pCurrent->m_pRight)
			ConvertNode(pCurrent->m_pRight,pLastNodeInList);
	}
};

测试用例

功能测试(输入的二叉树完全二叉树;所有节点都没有左/右子树的二叉树;只有一个节点的二叉树)
特殊输入测试(指向二叉树根节点的指针为nullptr指针)

109-有序链表转换二叉搜索树

给定一个单链表的头节点 head ,其中的元素 按升序排序 ,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。
在这里插入图片描述

方法一:分治

具体地,设当前链表的左端点为 left,右端点right,包含关系为「左闭右开」,即 left 包含在链表中而right 不包含在链表中。我们希望快速地找出链表的中位数节点 mid。

为什么要设定「左闭右开」的关系?由于题目中给定的链表为单向链表,访问后继元素十分容易,但无法直接访问前驱元素。因此在找出链表的中位数节点mid 之后,如果设定「左闭右开」的关系,我们就可以直接用(left,mid) 以及 (mid.next,right) 来表示左右子树对应的列表了。并且,初始的列表也可以用(head,null) 方便地进行表示,其中null 表示空节点。

找出链表中位数节点的方法多种多样,其中较为简单的一种是「快慢指针法」。初始时,快指针fast 和慢指针 slow 均指向链表的左端点left。我们将快指针 fast 向右移动两次的同时,将慢指针 slow 向右移动一次,直到快指针到达边界(即快指针到达右端点或快指针的下一个节点是右端点)。此时,慢指针对应的元素就是中位数。

在找出了中位数节点之后,我们将其作为当前根节点的元素,并递归地构造其左侧部分的链表对应的左子树,以及右侧部分的链表对应的右子树。

class Solution {
public:
    ListNode* getMedian(ListNode* left, ListNode* right) {
        ListNode* fast = left;
        ListNode* slow = left;
        while (fast != right && fast->next != right) {
            fast = fast->next;
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }

    TreeNode* buildTree(ListNode* left, ListNode* right) {
        if (left == right) {
            return nullptr;
        }
        ListNode* mid = getMedian(left, right);
        TreeNode* root = new TreeNode(mid->val);
        root->left = buildTree(left, mid);
        root->right = buildTree(mid->next, right);
        return root;
    }

    TreeNode* sortedListToBST(ListNode* head) {
        return buildTree(head, nullptr);
    }
};
  1. 时间复杂度:O(nlogn),其中 n 是链表的长度。设长度为 n 的链表构造二叉搜索树的时间为 T(n),递推式为 T(n)=2⋅T(n/2)+O(n),根据主定理,T(n)=O(nlogn)。
  2. 空间复杂度:O(logn),这里只计算除了返回答案之外的空间。平衡二叉树的高度为 O(logn),即为递归过程中栈的最大深度,也就是需要的空间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我可以为您介绍一些常见的数据结构与算法面试题。以下是一些常见的数据结构与算法面试题及其解答: 1. 请解释什么是数组? 数组是一种线性数据结构,它由相同类型的元素组成,并按照一定的顺序存储在连续的内存空间中。数组可以通过索引来访问和修改其中的元素。 2. 请解释什么是链表链表是一种线性数据结构,它由节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表的优点是插入和删除操作的时间复杂度为O(1),但访问元素的时间复杂度为O(n)。 3. 请解释什么是栈和队列? 栈和队列都是常见的数据结构。 - 栈是一种后进先出(LIFO)的数据结构,只允许在栈顶进行插入和删除操作。 - 队列是一种先进先出(FIFO)的数据结构,允许在队尾进行插入操作,在队头进行删除操作。 4. 请解释什么是二叉树二叉树是一种特殊的树结构,每个节点最多有两个子节点。二叉树可以分为二叉搜索树、平衡二叉树、完全二叉树等不同类型。 5. 请解释什么是排序算法? 排序算法是将一组数据按照一定的顺序进行排列的算法。常见的排序算法有冒泡排序、插入排序、选择排序、快速排序、归并排序等。 6. 请解释什么是动态规划? 动态规划是一种解决多阶段决策问题的优化方法。它通过将问题分解为多个子问题,并保存子问题的解,避免重复计算,从而提高算法的效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值