题目:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
解题思路
首先,让我们来回顾一下搜索二叉树的结构一些相关特点:
(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);
}
};
- 时间复杂度:O(nlogn),其中 n 是链表的长度。设长度为 n 的链表构造二叉搜索树的时间为 T(n),递推式为 T(n)=2⋅T(n/2)+O(n),根据主定理,T(n)=O(nlogn)。
- 空间复杂度:O(logn),这里只计算除了返回答案之外的空间。平衡二叉树的高度为 O(logn),即为递归过程中栈的最大深度,也就是需要的空间。