题目描述

image.png

解题思路

详解🔗
和合并2个有序列表思路一致。只不过是合并k个。
image.png
合并2个有序链表:

  1. public ListNode mergeTwoLists(ListNode a, ListNode b) {
  2. if (a == null || b == null) {
  3. return a != null ? a : b;
  4. }
  5. ListNode head = new ListNode(0);
  6. ListNode tail = head, aPtr = a, bPtr = b;
  7. while (aPtr != null && bPtr != null) {
  8. if (aPtr.val < bPtr.val) {
  9. tail.next = aPtr;
  10. aPtr = aPtr.next;
  11. } else {
  12. tail.next = bPtr;
  13. bPtr = bPtr.next;
  14. }
  15. tail = tail.next;
  16. }
  17. tail.next = (aPtr != null ? aPtr : bPtr);
  18. return head.next;
  19. }

image.png

顺序合并

每次遍历就合并一个链表,一次合并。

  1. public ListNode mergeKLists(ListNode[] lists) {
  2. if (lists == null && lists.length == 0) return null;
  3. if (lists.length == 1) return lists[0];
  4. ListNode head = lists[0];
  5. ListNode cur = head;
  6. for (int i = 1; i < lists.length; i++) {
  7. ListNode node = lists[i];
  8. ListNode newHead = new ListNode(0);
  9. ListNode newCur = newHead;
  10. while (cur != null && node != null) {
  11. if (cur.val < node.val) {
  12. newCur.next = cur;
  13. cur = cur.next;
  14. newCur = newCur.next;
  15. }else {
  16. newCur.next = node;
  17. node = node.next;
  18. newCur = newCur.next;
  19. }
  20. }
  21. newCur.next = cur == null ? node : cur;
  22. cur = newHead.next;
  23. }
  24. return cur;
  25. }

官解:
我们可以想到一种最朴素的方法:用一个变量 ans 来维护以及合并的链表,第 i 次循环把第 i 个链表和 ans 合并,答案保存到 ans 中。

  1. class Solution {
  2. public ListNode mergeKLists(ListNode[] lists) {
  3. ListNode ans = null;
  4. for (int i = 0; i < lists.length; ++i) {
  5. ans = mergeTwoLists(ans, lists[i]);
  6. }
  7. return ans;
  8. }
  9. public ListNode mergeTwoLists(ListNode a, ListNode b) {
  10. if (a == null || b == null) {
  11. return a != null ? a : b;
  12. }
  13. ListNode head = new ListNode(0);
  14. ListNode tail = head, aPtr = a, bPtr = b;
  15. while (aPtr != null && bPtr != null) {
  16. if (aPtr.val < bPtr.val) {
  17. tail.next = aPtr;
  18. aPtr = aPtr.next;
  19. } else {
  20. tail.next = bPtr;
  21. bPtr = bPtr.next;
  22. }
  23. tail = tail.next;
  24. }
  25. tail.next = (aPtr != null ? aPtr : bPtr);
  26. return head.next;
  27. }
  28. }

image.png

分治合并

image.png
我们可以一次合并一次的合并,递归到最后2个链表进行合并,合并成了k/2,然和在合并最后两个,合并成了k/2;
image.png
主要体现分而治之的是这句代码:
return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
mergeTwoLists函数是合并2个链表,两个链表继续递归,最终停止递归后返回合并的链表,在2个链表进行合并。

  1. // 分治
  2. // 分治
  3. public ListNode mergeKLists(ListNode[] lists) {
  4. return merge(lists, 0, lists.length - 1);
  5. }
  6. public ListNode merge(ListNode[] lists, int l, int r) {
  7. if (l == r) return lists[l];
  8. if (l > r) return null;
  9. // 分儿治之
  10. int mid = (l + r) >> 1;
  11. // 左闭右开
  12. return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
  13. }
  14. public ListNode mergeTwoLists(ListNode A, ListNode B) {
  15. if (A == null || B == null) return A == null ? B : A;
  16. ListNode dummy = new ListNode(0);
  17. ListNode cur = dummy, a = A, b = B;
  18. while (a != null && b != null) {
  19. if (a.val < b.val) {
  20. cur.next = a;
  21. a = a.next;
  22. } else {
  23. cur.next = b;
  24. b = b.next;
  25. }
  26. cur = cur.next;
  27. }
  28. cur.next = a == null ? b : a;
  29. return dummy.next;
  30. }

image.png

使用优先队列合并

可以使用一个优先队列记录每个节点的的头元素,注意优先队列需要小的在前面,因为合并成升序,出队便是最小的。注意记录的知识头节点,所以头节点加入节点之后,next不为空时还需要入队列。

  1. // 优先级队列
  2. public ListNode mergeKLists(ListNode[] lists) {
  3. PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>((a, b) -> a.val - b.val);
  4. ListNode dummy = new ListNode(0);
  5. ListNode cur = dummy;
  6. // 将每个链表都节点按照从小到大放入队列
  7. for (ListNode node : lists) {
  8. if (node != null) {
  9. queue.offer(node);
  10. }
  11. }
  12. while (!queue.isEmpty()) {
  13. ListNode node = queue.poll();
  14. cur.next = node;
  15. cur = cur.next;
  16. // 如果node节点不为空,继续入队
  17. if (node.next != null) {
  18. queue.offer(node.next);
  19. }
  20. }
  21. return dummy.next;
  22. }