大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。
题目
23. 合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入: lists = [[1,4,5],[1,3,4],[2,6]]
输出: [1,1,2,3,4,4,5,6]
解释: 链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入: lists = []
输出: []
示例 3:
输入: lists = [[]]
输出: []
提示:
k == lists.length0 <= k <= 10^40 <= lists[i].length <= 500-10^4 <= lists[i][j] <= 10^4lists[i]按 升序 排列lists[i].length的总和不超过10^4
暴力解法
思路
- 对
lists中的数据进行过滤,过滤掉空节点; - 找出循环结束条件 --
lists.length <= 1, 当只剩下一个链表直接插入末尾即可; - 创建一个新链表
result用来存储排序好的结果, 同时添加变量prev指向排序好的末尾节点; - 每一轮相当于对数组做一个取最小值的操作,然后把最小值插到
prev后面; - 找出最小值后,还需要判断最小值所在的链表还有没有剩余元素,有的话就执行抬走,下一位。没有的话就把空链表踢出数组。
实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function(lists) {
let result = { val: 0, next: null };
let prev = result;
lists = lists.filter(item => item);
while (lists.length > 1) {
const n = lists.length;
// 找最小值所在的链表
let minIndex = 0, minValue = lists[0].val;
for (let i = 1; i < n; i++) {
if (lists[i].val < minValue) {
minValue = lists[i].val;
minIndex = i;
}
}
const cur = lists[minIndex];
const next = cur.next;
// 抬走,下一位。没有下一位就整个链表抬走
if (next) {
lists[minIndex] = next;
} else {
lists.splice(minIndex, 1);
}
// 每一轮把最小值插入最后面,就实现了排序
prev.next = cur;
prev = prev.next;
}
// 有可能一开始就是空数组,lists[0]为undefined节点执行Null
prev.next = lists[0] || null;
return result.next;
};
耗时
优化
在上述解法过程中, 我们发现我们每次都要拿列表中的所有元素出来比较,取最大值。这样子做了很多重复的比较,所以我做了如下优化:
- 把所有链表根据头节点的值大小做个排序,放到数组
arr中; - 每次取出
arr中的第一个元素就是最小值,同时把第一个元素的链表指针向后传递; - 如果链表为空就踢出数组中,否则的话就做一轮插入排序,把当前值放到合适的位置;
- 这样子一来没轮我们只需要进行O(logn)次遍历即可,而数组的第一位始终是最小值。
优化代码
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function(lists) {
let result = { val: 0, next: null };
let prev = result;
lists = lists.filter(item => item);
// 这里先做好排序
lists.sort((a, b) => a.val - b.val);
while (lists.length > 1) {
const n = lists.length;
// 数组已经排序好了, 第一个就是最小值
let cur = lists[0];
const next = cur.next;
// 抬走,下一位。没有下一位就整个链表抬走
if (next) {
lists[0] = next;
// 插入排序
for (let i = 1; i < n; i++) {
if (lists[i].val < lists[i - 1].val) {
// 交换位置
[lists[i], lists[i - 1]] = [lists[i - 1], lists[i]]
} else {
break;
}
}
} else {
lists.splice(0, 1);
}
// 每一轮把最小值插入最后面,就实现了排序
prev.next = cur;
prev = prev.next;
}
// 剩余的接在尾部,有可能一开始就是空数组,lists[0]为undefined节点赋值Null
prev.next = lists[0] || null;
return result.next;
};
耗时
进阶
堆排序
我们可以构建一个最小堆,每次取出堆顶的一个元素来构建链表。然后用完了如果链表还有后继节点再塞回去堆里面。一直循环到拿完链表的所有元素为止。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function(lists) {
let result = new ListNode(0);
let prev = result;
let minHeap = new MinPriorityQueue({ priority: (bid) => bid.val });
// 构建最小堆
for (let i = 0; i < lists.length; i++) {
if (lists[i]) {
minHeap.enqueue(lists[i]);
}
}
// 如果长度不为0的时候,每次拿最小值出来构建新链表
while (minHeap.size()) {
let cur = minHeap.dequeue().element;
const next = cur.next;
cur.next = null;
prev.next = cur;
prev = prev.next;
// 如果还有值,塞回去继续排
if (next) {
minHeap.enqueue(next);
}
}
return result.next;
};
堆排序结果
归并排序
对链表两两进行排序,直到最后剩下一个链表直接返回即可。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function(lists) {
// 归并排序两两合并
while (lists.length > 1) {
const len = Math.ceil(lists.length / 2);
for (let i = 0; i < len; i++) {
lists[i] = mergeTwoList(lists[2 * i], lists[2 * i + 1]);
}
lists.length = len;
}
return lists[0] || null;
};
// 合并两个有序链表
function mergeTwoList(list1, list2) {
if (!list2) return list1;
let result = new ListNode(0);
let prev = result;
while (list1 && list2) {
if (list2.val < list1.val) {
const next = list2.next;
list2.next = null;
prev.next = list2;
list2 = next;
} else {
const next = list1.next;
list1.next = null;
prev.next = list1;
list1 = next;
}
prev = prev.next;
}
prev.next = list1 || list2;
return result.next;
}
归并排序结果
总结
合并有序链表题目用归并排序性能最高,以后遇到这种题目我就直接归并排序梭哈了。
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。