学习记录:js算法(三十四):合并 K 个升序链表

合并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 = [[]]
输出:[]

思路一

var mergeKLists = function(lists) {
  if (lists.length === 0) return null;
  if (lists.length === 1) return lists[0];

  const mid = Math.floor(lists.length / 2);
  const left = mergeKLists(lists.slice(0, mid));
  const right = mergeKLists(lists.slice(mid));

  return mergeTwoLists(left, right);
};

// Helper function to merge two sorted lists
function mergeTwoLists(l1, l2) {
  const dummy = new ListNode(0);
  let current = dummy;

  while (l1 && l2) {
    if (l1.val < l2.val) {
      current.next = l1;
      l1 = l1.next;
    } else {
      current.next = l2;
      l2 = l2.next;
    }
    current = current.next;
  }

  // Attach any remaining elements
  current.next = l1 || l2;

  return dummy.next;
}

讲解

  1. 分解问题:将所有链表分为两组,递归地合并每一组中的链表。
  2. 合并两组:当每一组中的链表已经被合并,再将这两组链表合并。
  3. 基线条件:如果链表数组中只有一个链表,直接返回这个链表;如果没有链表,返回null。

以下是代码详细分析:

  1. 主函数 mergeKLists
var mergeKLists = function(lists) {
 	if (lists.length === 0) return null; // 如果链表数组为空,返回 null
 	if (lists.length === 1) return lists[0]; // 如果只有一个链表,直接返回该链表
 	const mid = Math.floor(lists.length / 2); // 找到中间位置
 	const left = mergeKLists(lists.slice(0, mid)); // 递归合并左半部分
 	const right = mergeKLists(lists.slice(mid)); // 递归合并右半部分
 	return mergeTwoLists(left, right); // 合并左半部分和右半部分
};

逻辑分析:

  • 边界条件:
    如果 lists 数组为空,返回 null
    如果 lists 数组中只有一个链表,直接返回该链表。
  • 分治法:
    通过找到中间索引 mid 将链表数组分为两部分。
    递归地对这两部分进行合并,最终调用 mergeTwoLists 合并两个已排序的链表。
  1. 辅助函数 mergeTwoLists
function mergeTwoLists(l1, l2) {
 	const dummy = new ListNode(0); // 创建一个虚拟头节点
 	let current = dummy; // 用于跟踪合并链表的当前节点
 	while (l1 && l2) { // 当两个链表都不为空时
   		if (l1.val < l2.val) { // 比较当前节点的值
    		 current.next = l1; // 将较小的节点连接到合并链表
    		 l1 = l1.next; // 移动 l1 到下一个节点
  		 } else {
     		current.next = l2; // 将较小的节点连接到合并链表
     		l2 = l2.next; // 移动 l2 到下一个节点
   		}
   		current = current.next; // 移动当前节点指针
	 }
	// 连接剩余的节点
	current.next = l1 || l2; // 如果 l1 或 l2 还有剩余节点,连接到合并链表
 	return dummy.next; // 返回合并链表的头节点(跳过虚拟头节点)
}

逻辑分析:

  • 虚拟头节点:
    创建一个虚拟头节点 dummy 用于简化合并操作,避免处理空链表的特殊情况。
  • 合并过程
    使用 while 循环遍历两个链表,比较当前节点的值,将较小的节点添加到合并链表中。
    当其中一个链表遍历完后,直接将另一个链表的剩余部分连接到合并链表的末尾。

思路二

var mergeKLists = function(lists) {
    const dummy = new ListNode(0);
    let current = dummy;
    const heap = [];

    // Helper function to compare nodes
    const compareNodes = (a, b) => a.val - b.val;

    // Initialize the heap with the first node of each list
    lists.forEach(list => {
        if (list) {
            heap.push(list);
        }
    });

    // Build the heap
    buildMinHeap(heap, compareNodes);

    // Extract the smallest element and rebuild the heap
    while (heap.length > 0) {
        const smallest = extractMin(heap, compareNodes);
        current.next = smallest;
        current = current.next;
        if (smallest.next) {
            heap.push(smallest.next);
            buildMinHeap(heap, compareNodes);
        }
    }

    return dummy.next;
};

// Helper functions for the heap operations
function buildMinHeap(array, compare) {
    const n = array.length;
    for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
        heapify(array, n, i, compare);
    }
}

function heapify(array, n, i, compare) {
    let smallest = i;
    const left = 2 * i + 1;
    const right = 2 * i + 2;

    if (left < n && compare(array[left], array[smallest]) < 0) {
        smallest = left;
    }

    if (right < n && compare(array[right], array[smallest]) < 0) {
        smallest = right;
    }

    if (smallest !== i) {
        [array[i], array[smallest]] = [array[smallest], array[i]];
        heapify(array, n, smallest, compare);
    }
}

function extractMin(array, compare) {
    const min = array[0];
    array[0] = array[array.length - 1];
    array.pop();
    heapify(array, array.length, 0, compare);
    return min;
}

讲解

  1. 初始化优先队列:创建一个最小堆(优先队列),并将每个链表的头节点(如果存在)插入堆中。堆中元素的比较基于链表节点的值。
  2. 提取最小元素:从堆中弹出最小元素,将其添加到结果链表中。
  3. 更新堆:如果弹出的节点有下一个节点,将其下一个节点插入堆中。
  4. 重复步骤2和3:直到堆为空,即所有节点都被处理完毕。
  5. 返回结果链表:返回结果链表的头节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值