Merge List
Basic Problem —— Merge Two Sorted Lists
Description
Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.
具体参见Leetcode
Analysis
首先明确题目要求,很简单,就是把两个有序链表重新合并成一个新的有序链表。注意,因为合并前两个链表都是有序的,所以我们不需要再对它们分别排序,我们只需要让它们合并即可,附加条件是仍然保持有序。而链表的合并与数组不一样,不需要再开辟新的空间,只需要在原来的已分配空间内对链表节点之间的链接指针进行改变操作即可。
具体而言,就是下图示例:
因为两个链表都是分别有序的,所以每次只需比较链表head指向的节点的value值即可。然后把小的节点连接到新的链表中,原来的head指针指向它的下一个节点,一直到有一个链表连接完成,剩下的那个链表的剩余节点就可以直接连接到新的链表上。
code
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) return l2;
if (l2 == NULL) return l1;
ListNode* first = NULL, *tmp = NULL;
while (l1 != NULL && l2 != NULL) {
if (l1->val > l2->val) {
if (first == NULL) {
first = tmp = l2;
} else {
tmp->next = l2;
tmp = l2;
}
l2 = l2->next;
} else {
if (first == NULL) {
first = tmp = l1;
} else {
tmp->next = l1;
tmp = l1;
}
l1 = l1->next;
}
}
if (l1 == NULL) {
while (l2 != NULL) {
tmp->next = l2;
tmp = l2;
l2 = l2->next;
}
}
if (l2 == NULL) {
while (l1 != NULL) {
tmp->next = l1;
tmp = l1;
l1 = l1->next;
}
}
return first;
}
};
Complexity
假设链表1有n个节点,链表2有m个节点。
时间复杂度:
因为是从头至尾遍历两个链表的,所以时间复杂度为
O(n+m)
。
空间复杂度:
在这个过程中因为并没有重新分配新的空间,还是操作指针,所以空间复杂度为
O(1)
。
Advanced Problem —— Merge k Sorted Lists
decription
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
具体参见Leetcode
Analysis
当k = 2 时就是上面的Merge Two Lists了,所以在这道题上是上面合并两个链表的延伸和推广。用k - 2 时的方法其实可以用于这道题的,但是过程相对比较复杂,需要注意的points还是有挺多的。
比较的对象变了。在前面的Basic版本中,我们每次只需要比较2个链表头指针指向的节点的value值,但是现在有k个链表,我们要怎样比较哪些值?
Maybe: 每一轮比较k个链表的头指针指向的节点,然后把value值最小的那个节点对应的链表index值标记下来
比较完把需要的节点拿出来后剩下的应该怎么处理?
Maybe: 把最小的节点拿出来以后,它所在的链表的头结点变成它指向的下一个节点。
如果k中的一个链表已经处理完了,应该标记好不删除还是直接删除?
Sure: 直接删除。我一开始没有删除,然后交上去直接Time Limit Exceeded,所以实践告诉我,一定要直接删除,理论上讲,如果不直接删除,每一轮遍历的数量保持在k,如果k很大,链表都很长,那么时间消耗肯定很大,所以直接删除才是最好的办法。
code
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.size() == 0) return NULL;
ListNode* min = NULL, *last = NULL, *first = NULL;
int len = lists.size();
int count = 0, index = 0, minIndex = 0;
vector<ListNode*> pointer;
for (int i = 0; i < len; i++) {
pointer.push_back(lists[i]);
}
while (lists.size()) {
if (index == lists.size()) {
index %= lists.size();
if (last != NULL) {
last->next = min;
}
else {
first = min;
}
last = min;
pointer[minIndex] = pointer[minIndex]->next;
min = pointer[index];
minIndex = index;
}
if (pointer[index] == NULL) {
lists.erase(lists.begin() + index);
pointer.erase(pointer.begin() + index);
}
else {
if (min == NULL) {
min = pointer[index];
minIndex = index;
}
else {
if (pointer[index]->val < min->val) {
min = pointer[index];
minIndex = index;
}
}
++index;
}
}
return first;
}
};
Complexity
- 时间复杂度:每挑选一个最小的节点需要一轮,每一轮遍历k个链表头,最坏的情况是k个链表长度相当,然后要依次挑n个节点,所以时间复杂度为 O(kn) 。
- 空间复杂度:开了一个指示链表head的数组,所以空间复杂度为 O(k) 。
Conclusion
从简单的两个有序链表的合并,继而推广到K个有序链表的合并,方法上可以一脉相承,尽管对于k个链表的情况该方法不是最优,但是胜在可思考性强,但是推广的时候注意一些特殊的、因为问题升级带来的小问题。最后,其实这种简单的方法是来自归并排序里的Merge阶段的合并算法。