原题: 链接
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
解答之前需要明白三个点:
- 链表是有序的
- 高度平衡的二叉搜索树
- 肯定用递归做简单地多
解法一
不难得知该树的根节点位于有序链表的中间位置。因此需要实现一个取出有序链表中间节点的算法,然后依次对取出的左半部分和右半部分递归执行类似的build tree操作。查找中间节点就是利用快慢指针法,取的一种自然的想法就是将链表斩断。由于链表不能像数组那样方便取值索引,因此在查找中间节点的时候,顺便保留中间节点的前一个节点,方便递归的时候直接拿过去享用。看代码会清楚一点:
# 一个查找中间节点 并返回它前一个节点的程序
def findMiddle(head):
pre, slow, fast = None, head, head
while fast and fast.next:
pre = slow
slow = slow.next
fast = fast.next.next
# 返回前一个节点 和 中间节点
return pre, slow
def sortedListToBST(head):
# 递归退出条件
if not head:
return None
# 只有一个节点的时候 也直接返回
if not head.next:
# 注意是返回树节点
return TreeNode(head.val)
pre, mid = findMiddle(head)
if pre:
# 斩断
pre.next = None
# 当下的根
root = TreeNode(mid.val)
# 递归
root.left = sortedListToBST(head)
root.right = sortedListToBST(mid.next)
return root
此算法时间复杂度为O(nlogn),空间复杂度为O(logn)。
一种空间换时间的算法就是通过将链表保存为数组,从而简化每次递归的查找操作。
解法二
def sortedListToBST(head):
arr = []
# 转换为数组
while head:
arr.append(head)
head = head.next
def helper(left, right):
if left >= right:
return None
mid = (left + right) >> 1
# 注意前闭后开的处理
root = TreeNode(mid.val)
root.left = helper(left, mid)
root.right = helper(mid+1, right)
return root
return helper(0, len(arr))
时间复杂度为O(n),空间复杂度也为O(n)。
解法三
我们联想到二叉树的中序遍历刚好也是满足升序,是否可以通过中序遍历的模板来解决重建二叉树的操作?
答案是肯定的。在递归形式的中序遍历下,每次处理的节点都和给定有序链表下的节点顺序相同。因此我们仅需要将某个节点有所建树(建立二叉树)的逻辑捋清楚了,然后将链表指向下一个节点即可,至于剩下的就交给递归去完成吧。
这就是二叉树递归中序遍历的魅力啊!
def sortedListToBST(head):
s = 0
# 基本逻辑同上面的算法
# 也需要拿到链表的长度
temp = head
while temp:
s += 1
temp = temp.next
def buildTree(left, right):
# 保存上一次递归完成的head
nonlocal head
if left >= right:
return None
mid = (left + right) >> 1
# 中序遍历模板
leftNode = buildTree(left, mid)
# 根节点
root = TreeNode(head.val)
root.left = leftNode
# 当前节点处理完毕 指向下一个待处理的节点
# 链表的有序保证了中序遍历恢复的正确性
head = head.next
root.right = buildTree(mid+1, right)
return root
return buildTree(0, s)
这是最好的算法了,其中时间复杂度为O(n),空间复杂度为O(logn)。