题目描述
今天做了一道hard的题目,感觉是之前的合并两个有序链表的升级版,提交了一次超时了,后来请教了梁老师改了一次节省了一些读取时间终于过了,同时也了解堆的用法,分治的算法虽然感觉没有完全弄懂,但是还是可以说明一下。
先放上最开始的版本:
# Definition for singly-linked list.
# ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
l = ListNode(0)
p = l
while lists:
a = []
for i in lists:
while i:
a.append(i.val)
s = a.index(min(a))
p.next = ListNode(a[s])
p = p.next
lists[s] = lists[s].next
return l.next
这是受到合并两个有序链表的启发,思路是什么呢?回顾一下,两个有序链表的时候是比较两个表头,谁小谁进入新的链表,同时被选中的链表的表头移出原来所在的链表,然后遍历完所有的链表元素,同理现在有K个,我们只需要比较K个链表的表头,找到最小的那个表头就可以了。
现在来解释一下我的代码,首先用a = [ ],来记录这K个表头,s = a.index(min(a)),用s记录列表中最小元素的下标,p.next = ListNode(a[s]),更新新的链表,lists[s] = lists[s].next,原链表去掉被选取的表头,while lists,知道lists中所有元素都被去除。(因为链表的最后一位是None)
思路其实很简单,但是这种方法会超时,为什么会超时呢?因为这里多用了一个空list a [ ]读取K个链表的表头,所以会慢,导致超时。于是不加存储直接提取最小表头就勉强不会超时,但是时间也很长,改进的代码如下:
# Definition for singly-linked list.
# ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
def function(s):
result=ListNode(0)
z=result
loc=0
sset=set(range(len(s)))
for i in range(len(s)):
if s[i] is None:
sset.remove(i)
while len(sset)>0:
minv=float("inf")
for i in sset:
if s[i].val<minv:
minv=s[i].val
loc=i
z.next=ListNode(minv)
z=z.next
if s[loc].next is None:
sset.remove(loc)
else:
s[loc]=s[loc].next
return result.next
return function(lists)
参考了题解之后,我发现是需要用堆排序来做会速度很多,但是从原理上讲,这两个算法的时间复杂度是差不多的,只是因为堆结构在python内是C++内置好的结构,所以运行速度会有所提升。
关于堆的概念,就是根节点必定小于子节点,其实就一棵带有大小关系的二叉树,可以在网上找一下。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
def function(s):
result=ListNode(0)
z=result
h=[]
for i in range(len(s)):
if s[i] is not None:
#用一个二维数组去记录表头值和是第几个链表的表头
h.append([s[i].val,i])
#建堆
heapq.heapify(h)
while len(h)>0:
#弹出堆上的最小值
tmp=heapq.heappop(h)
z.next=ListNode(tmp[0])
z=z.next
if s[tmp[1]].next is not None:
s[tmp[1]]=s[tmp[1]].next
#堆上插入被弹出表头的链表的下一个表头
heapq.heappush(h,[s[tmp[1]].val,tmp[1]])
return result.next
return function(lists)
还有一种方法就是分治:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
if not lists:return
n = len(lists)
return self.merge(lists, 0, n-1)
def merge(self,lists, left, right):
if left == right:
return lists[left]
mid = left + (right - left) // 2
l1 = self.merge(lists, left, mid)
l2 = self.merge(lists, mid+1, right)
return self.mergeTwoLists(l1, l2)
def mergeTwoLists(self,l1, l2):
if not l1:return l2
if not l2:return l1
if l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
这个方法可以说是非常的精髓了,完美运用上了之前的合并两个有序链表那个题目。
我们看一下这个mergeTwoLists就是之前合并两个有序链表的答案,我们知道了这个函数的功能是合并两个有序链表。
OK,那这个merge函数是在干什么呢?
首先我们看到,它的输入是lists,left,right
lists是K个链表储存在lists这个列表中
left,right是指从第left个链表开始合并到第right个链表
举个例子让大家好理解,比如lists = [1,2,3,4]
这个1,2,3,4只是一个编号,代表的是一个链表
return self.merge(lists, 0, n-1)
这里就是开始调用merge(lists,0,3)
mid = 0 + (3-0)//2 = 1
l1 = merge(lists,0,1)
mid_l1 = 0 + (1-0)//2 = 0
if left == right:
return lists[left]
l1_1 = merge(lists,0,0) = lists[0]
l1_2 = merge(lists,1,1) = lists[1]
return self.mergeTwoLists(l1, l2)
l1 = 由lists[0],lists[1]合并的有序链表
同理
l2 = merge(lists,2,3)
mid_l2 = 2 + (3-2)//2 = 2
l2_1 = merge(lists,2,2) = lists[2]
l2_2 = merge(lists,3,3) = lists[3]
l2 = 由lists[2],lists[3]合并的有序链表
最后
return self.mergeTwoLists(l1, l2)
return的就是由l1,l2,合并的有序链表,等价于由lists[0],lists[1],lists[2],lists[3]合并的有序链表
再如lists = [1,2,3,4,5]这种个数为奇数的情况
mid = 0 + (4-0)//2 = 2
l1 = merge(lists,0,2)
mid_l1 = 0 + (2-0)//2 = 1
l1_1 = merge(lists,0,1)
mid_l1_1 = 0 + (1-0)//2 = 0
l1_1_1 = merge(lists,0,0) = lists[0]
l1_1_2 = merge(lists,1,1) = lists[1]
所以l1_1 = 由lists[0],lists[1]合并的有序链表
l1_2 = merge(lists,2,2) = lists[2]
所以l1 = 由l1_1,l1_2合并的有序链表,等价于由lists[0],lists[1],lists[2]合并的有序链表
l2 = merge(lists,3,4)
mid_l2 = 3 + (4-3)//2 = 3
l2_1 = merge(lists,3,3) = lists[3]
l2_2 = merge(lists,4,4) = lists[4]
所以l2 = 由lists[3],lists[4]合并的有序链表
所以return的是由lists[0],lists[1],lists[2]lists[3],lists[4]合并的有序链表
所以我们可以看到这个merge函数就是利用二分将所有left right之间的链表全部连起来,而且是两两相连。