题目描述与分析
合并k个有序链表。假设每个链表的长度都是n来分析复杂度吧。
这一题是LeetCode的第23题。类似于归并排序(Merge Sort)最后的合并步骤。
朴素Naive的实现是每次合并一个list进来,时间复杂度是 O(kn2) 的。
如果能够每次快速取到所有lists的最小值中的最小值就可以快速合并了。那么用堆或者优先队列来按照值的大小维护所有lists的head,就可以在 O(1) 的时间内取到当前最小值,然后把这个值的next入堆并维护堆即可,维护堆的复杂度是 O(log(k)) 。初始化堆是线性的, O(k) . 所有总的时间复杂度应该是 O(knlog(k)) 。
坑
最初写的时候遇到了一些问题没有注意。主要就是
1. k=0
2. 链表为空
最后还是参考了别人的边界情况才明白LeetCode里的输入格式是什么意思。需要做的就是提前扫描一遍k个lists把空的list忽略。
C++的实现
c++ 的 stl 中的make_heap, push_heap, pop_heap 这些也是第一次用,参考了:
1.cplusplus.com/…/make_heap
2. lengbingshy的博客:C++中定义比较函数的三种方法
STL的 Heap 实现
Error: elements in iterator range [__first, __last) do not form a heap.
需要注意的是在使用自定义heap的比较函数的时候,调用push_heap()和pop_heap()的时候也要传入比较函数,不然按照默认的比较函数就会报”不是heap”的错。
Code
#include <algorithm>
#include <vector>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
bool cmp(ListNode *left, ListNode *right) {
return left->val > right->val;
}
// Solution class for leetcode
class Solution {
public:
ListNode *mergeKLists(vector <ListNode *>& lists) {
vector<ListNode *>v;
for (int i = 0; i < lists.size(); i++) {
if (lists[i] != NULL)
v.push_back(lists[i]);
}
if (v.size() == 0) return NULL;
if (v.size() == 1) return v[0];
ListNode head(0);
ListNode *tail = NULL;
head.val = 0;
head.next = NULL;
tail = &head;
// init heap by cmp function
make_heap(v.begin(), v.end(), cmp);
while (v.size() > 0)
{
ListNode *front = v.front();
pop_heap(v.begin(), v.end(), cmp); // pop using cmp function
v.pop_back();
if (front->next != NULL)
{
v.push_back(front->next);
push_heap(v.begin(), v.end(), cmp); // push using cmp function
}
front->next = NULL;
tail->next = front;
tail = tail->next;
}
return head.next;
}
};
STL 的 priority_queue 实现
参考
- cplusplus.com : priority_queue
- priority_queue 的 constructor
为了熟悉priority_queue的用法又实现了一下。但是也是遇到了意料之外的情况。
第一版实现 Time Limit Exceeded
class CompareListNodePtr {
bool reverse;
public:
CompareListNodePtr(const bool & _reverse = false) {
reverse = _reverse;
}
bool operator ()(const ListNode *left, const ListNode *right) {
if (reverse) return left->val > right->val;
else return left->val < right->val;
}
};
typedef priority_queue<ListNode *, vector<ListNode *>, CompareListNodePtr > ListNodePQ;
class Solution {
public:
ListNode *mergeKLists(vector <ListNode *>& lists) {
ListNode head(0);
ListNode * tail = &head;
ListNodePQ pq(CompareListNodePtr(true));
for (int i = 0; i < lists.size(); i++)
{
if (lists[i] != NULL) pq.push(lists[i]);
}
if (pq.size() == 0) return NULL;
if (pq.size() == 1) return pq.top();
while (! pq.empty()) {
ListNode * t = pq.top();
pq.pop();
if (t->next) pq.push(t->next);
tail->next = t;
t->next = NULL;
tail = tail->next;
}
return head.next;
}
};
发现TLE之后就想起了数据结构课上学过的东西,就是直接顺序插入堆来建立优先队列的复杂度稍高,而 make_heap 的实现是可以做到线性建堆的。所以正确的做法应该是把作为Container的vector传给priority_queue来建立堆。
第二版实现Accepted
class CompareListNodePtr {
bool reverse;
public:
CompareListNodePtr(const bool & _reverse = false) {
reverse = _reverse;
}
bool operator ()(const ListNode *left, const ListNode *right) {
if (reverse) return left->val > right->val;
else return left->val < right->val;
}
};
typedef priority_queue<ListNode *, vector<ListNode *>, CompareListNodePtr > ListNodePQ;
class Solution {
public:
ListNode *mergeKLists(vector <ListNode *>& lists) {
ListNode head(0);
ListNode * tail = &head;
vector<ListNode *> v;
for (int i = 0; i < lists.size(); i++)
{
if (lists[i] != NULL) v.push_back(lists[i]);
}
if (v.size() == 0) return NULL;
if (v.size() == 1) return v.front();
ListNodePQ pq(v.begin(), v.end(), CompareListNodePtr(true));
while (! pq.empty()) {
ListNode * t = pq.top();
pq.pop();
if (t->next) pq.push(t->next);
tail->next = t;
t->next = NULL;
tail = tail->next;
}
return head.next;
}
};
Golang 的实现
“container/heap”
Golang 中也有实现好的容器可以用, 搜索一下找到文档(golang.org 貌似目前也被墙了。但是用go自带的godoc命令在本地查看文档也可以)就可以照着写了。把对应的Type实现heap的interface就可以了。
参考的 ListNodeHeap 实现
import "container/heap"
// reference: golang.org/pkg/container/heap/#example__intHeap
type ListNode struct {
Val int
Next *ListNode
}
type ListNodeHeap []*ListNode
func (h ListNodeHeap) Len() int {
return len(h)
}
func (h ListNodeHeap) Less(i, j int) bool {
return h[i].Val < h[j].Val
}
func (h ListNodeHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *ListNodeHeap) Push(x interface{}) {
*h = append(*h, x.(*ListNode))
}
func (h *ListNodeHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
Time Efficiency Comparison
C++ is slower than Golang implementation. Maybe STL is really slow than straight forward implementation.