跳到主要内容

合并题库

题一、合并两个有序链表


将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例1

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例2

输入:l1 = [], l2 = []
输出:[]

方案A、递归

思路

  • 如果 l1 和 l2 为空链表,没有任何操作需要合并
  • 判断 l1 和 l2 哪一个链表的值更小,然后递归地决定下一个添加到结果里的节点。
  • 如果两个链表有一个为空,递归结束。

实现

function mergeTwoLists(listA, listB) {
if (listA === null) {
return listB;
} else if (listB === null) {
return listA;
} else if (listA.val < listB.val) {
listA.next = mergeTwoLists(listA.next, listB);
return listA;
} else {
listB.next = mergeTwoLists(listA, listB.next);
return listB;
}
}

性能分析:

时间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度。因为每次调用递归都会去掉 l1 或者 l2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即 O(n+m)。

空间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度。递归调用 mergeTwoLists 函数时需要消耗栈空间,栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n+mn+m 次,因此空间复杂度为 O(n+m)。

方案B、迭代

思路

当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。

实现

function mergeTwoLists(listA, listB) {
if (!listA) {
return listB;
}
if (!listB) {
return listA;
}
const prevHead = new ListNode(-1);
let prev = prevHead;
while (listA != null && listB != null) {
if (listA.val <= listB.val) {
prev.next = listA;
listA = listA.next;
} else {
prev.next = listB;
listB = listB.next;
}
prev = prev.next;
}
prev.next = listA == null ? listB : listA;
return prevHead.next;
}

性能分析:

时间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度。因为每次循环迭代中,l1 和 l2 只有一个元素会被放进合并链表中, 因此 while 循环的次数不会超过两个链表的长度之和。所有其他操作的时间复杂度都是常数级别的,因此总的时间复杂度为 O(n+m)。

空间复杂度:O(1)。我们只需要常数的空间存放若干变量。

题二、合并K个升序链表


给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例一

输入: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

示例二

输入:lists = []
输出:[]

方案A、顺序合并

function mergeTwoLists(listA,listB){
if(listA == null || listB == null){
return listA == null ? listB : listA;
}
const prevHead = new ListNode(-1);
let prev = prevHead;
while(listA != null && listB != null){
if(listA.val <= listB.val){
prev.next = listA;
listA = listA.next;
}else{
prev.next = listB;
listB = listB.next;
}
prev = prev.next;
}
prev.next = listA == null ? listB : listA;
return prevHead.next;
}
function mergeKLists(lists){
let result = null;
for(let i=0;i<lists.length;i++){
result = mergeTwoLists(result,lists[i]);
}
return result;
}

方案B、分治合并

function mergeTwoLists(listA,listB){
if(listA == null || listB == null){
return listA == null ? listB : listA;
}
const prevHead = new ListNode(-1);
let prev = prevHead;
while(listA != null && listB != null){
if(listA.val <= listB.val){
prev.next = listA;
listA = listA.next;
}else{
prev.next = listB;
listB = listB.next;
}
prev = prev.next;
}
prev.next = listA == null ? listB : listA;
return prevHead.next;
}
function merge(lists,left,right){
if(left == right){
return lists[left]
}
if(left > right){
return null;
}
let middle = (left + right) >> 1;
return mergeTwoLists(merge(lists,left,middle),merge(lists,middle+1,right));
}
function mergeKLists(lists){
return merge(lists,0,lists.length-1);
}

方案C、使用优先队列合并

class PriorityQueue {
constructor() {
this.length = 0;
this.data = [];
}
compareTo(a, b) {
return a.val - b.val;
}
shiftUp(index) {
while (index > 0) {
let parent = Math.floor((index - 1) / 2);
if (this.compareTo(this.data[index], this.data[parent]) < 0) {
[this.data[index], this.data[parent]] = [
this.data[parent],
this.data[index],
];
index = parent;
} else {
return;
}
}
}
shiftDown(index) {
while (index * 2 + 1 <= this.length - 1) {
let child = index * 2 + 1;
if (
child + 1 <= this.length - 1 &&
this.compareTo(this.data[child], this.data[child + 1]) > 0
) {
child++;
}
if (this.compareTo(this.data[index], this.data[child]) > 0) {
[this.data[index], this.data[child]] = [
this.data[child],
this.data[index],
];
index = child;
} else {
return;
}
}
}
add(value) {
this.data.push(value);
this.length++;
if (this.length != 1) {
this.shiftUp(this.length - 1);
}
}
poll() {
const result = this.data[0];
if (this.length == 1) {
this.data.pop();
} else {
this.data[0] = this.data.pop();
}
this.length--;
this.shiftDown(0);
return result;
}
peek() {
return this.data[0];
}
isEmpty() {
return this.data.length === 0;
}
}

class Status {
constructor(val, node) {
this.val = val;
this.node = node;
}
}

const queue = new PriorityQueue();

function mergeKLists(lists) {
for (let item of lists) {
if (item != null) {
queue.add(new Status(item.val, item));
}
}
let prevHead = new ListNode(-1);
let prev = prevHead;
while (!queue.isEmpty()) {
const status = queue.poll();
prev.next = status.node;
prev = prev.next;
if (status.node.next != null) {
queue.add(new Status(status.node.next.val, status.node.next));
}
}
return prevHead.next;
}