力扣题库109,将有序链表转换为二叉搜索树。
题目链接点这里。
题目描述
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
解题思路
这一题和108题非常相似,不同的是108中的数据类型为数组,而109这里变成了链表。
思路也类似,通过寻找中点来进行赋值,同时利用递归完成目标。但是这里遇到的一个问题就是链表的中点不像列表那么容易寻找。
通过快慢指针的方式可以找到链表的中点。一开始有两个指针slow和fast分别指向链表的头,之后slow每移动一位,fast移动两位,一直到fast不能再移动为止,slow指向的就是链表的中点。
找到中点之后,还需要将中点前一位的next指向None,才能再带入递归,以避免无限循环下去。
同样需要注意的是递归的退出条件。
初始答案
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sortedListToBST(self, head: ListNode) -> TreeNode:
if not head:
return None
if head.next is None:
return TreeNode(val=head.val)
if head.next.next is None:
node = TreeNode(head.next.val)
node.left = TreeNode(val=head.val)
return node
slow = head
fast = head
pre = head
while fast.next and fast.next.next:
pre=slow
slow=slow.next
fast=fast.next.next
mid = slow
pre.next = None
node = TreeNode(val=mid.val)
node.left = self.sortedListToBST(head)
node.right = self.sortedListToBST(slow.next)
return node
这里的快慢指针代码如下
slow = head
fast = head
pre = head
while fast.next and fast.next.next:
pre=slow
slow=slow.next
fast=fast.next.next
mid = slow
pre.next = None
而递归的退出条件有三个
if not head:
return None
if head.next is None:
return TreeNode(val=head.val)
if head.next.next is None:
node = TreeNode(head.next.val)
node.left = TreeNode(val=head.val)
return node
当传入的是空链表时,返回None。当传入的链表只有1位或者2位时,就不需要再进行快慢指针的操作了,直接赋值返回就行了。只有当链表的位数打过2位时才进行底下的一系列操作。当然,后两种退出条件也可以在后面通过slow==fast
来处理。
因为每一轮递归都对所有元素遍历来寻找中点,一共寻找了logN轮,所以时间复杂度为O(NlogN)。提交以后执行用时: 152 ms,击败了46%的提交。
改进答案
时间复杂度为O(NlogN)显然并不是太理想,看到解题区的大神又给出了下面这种思路。
既然是按照升序排列的链表,那其实就和BST按照中序遍历的返回结果是一样的。那么同样是进行递归,不过这次递归的时候是按照中序遍历的方式来进行,每一次都是先处理左子树,然后是根节点,然后是右子树。
注意,下面是关键。递归第一次退出之后,开始对最左边的叶子节点的根节点赋值,此时赋值链表的第一个值。然后在递归栈不停回退的时候,链表依次往下对每次的根节点进行赋值。
这么说可能不够直观,直接看代码。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sortedListToBST(self, head: ListNode) -> TreeNode:
def getLength(head:ListNode) -> int:
len = 0
while head:
len+=1
head=head.next
return len
def buildBST(start:int,end:int) -> TreeNode:
if start > end:
return None
mid = (start + end+1)//2
node = TreeNode()
node.left = buildBST(start,mid-1)
nonlocal head
node.val = head.val
head=head.next
node.right = buildBST(mid+1,end)
return node
length = getLength(head)
return buildBST(1,length)
首先是两个辅助函数,一个是计算链表的长度,另一个是递归的主体。
重点看递归的主体部分,每次都是先执行node.left = buildBST(start,mid-1)
,也就是说还没等到真正的赋值,就已经递归到下一层了。一直到退出递归,开始第一次执行node.val = head.val
的时候是在最左侧的叶节点。再执行node.right = buildBST(mid+1,end)
对最左侧叶节点的右子节点进行同样的操作。返回以后是倒数第二层的根节点,之后是倒数第二层的右子节点,一直到真正的根节点。每次需要赋值的时候就获取链表的内容,同时往前移动一位。注意因为需要操作外层变量,所以要用nonlocal head
进行外部变量声明。这是python3中引入的操作。
因为只是计算了一次总长度,所以时间复杂度为O(N)。不过提交以后执行时间还是152ms,没有什么提升。
注意事项
不管是108还是109,答案都并不唯一,例如在改进答案中用mid = (start + end+1)//2
或者mid=(start+end)//2
都是正确的,重点关注的是解题思路。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。