1. 题目描述(困难难度)

23. Merge k Sorted Lists - 图1

k 个有序链表的合并。

我们用 N 表示链表的总长度,考虑最坏情况,k 个链表的长度相等,都为 n 。

解法一 暴力破解

简单粗暴,遍历所有的链表,将数字存到一个数组里,然后用快速排序,最后再将排序好的数组存到一个链表里。

  1. public ListNode mergeKLists(ListNode[] lists) {
  2. List<Integer> l = new ArrayList<Integer>();
  3. //存到数组
  4. for (ListNode ln : lists) {
  5. while (ln != null) {
  6. l.add(ln.val);
  7. ln = ln.next;
  8. }
  9. }
  10. //数组排序
  11. Collections.sort(l);
  12. //存到链表
  13. ListNode head = new ListNode(0);
  14. ListNode h = head;
  15. for (int i : l) {
  16. ListNode t = new ListNode(i);
  17. h.next = t;
  18. h = h.next;
  19. }
  20. h.next = null;
  21. return head.next;
  22. }

时间复杂度:假设 N 是所有的数字个数,存到数组是 O(N),排序如果是用快速排序就是

23. Merge k Sorted Lists - 图2

,存到链表是 O(N),所以取个最大的,就是

23. Merge k Sorted Lists - 图3

空间复杂度:新建了一个链表,O(N)。

解法二 一列一列比较

23. Merge k Sorted Lists - 图4

我们可以一列一列的比较,将最小的一个存到一个新的链表里。

  1. public ListNode mergeKLists(ListNode[] lists) {
  2. int min_index = 0;
  3. ListNode head = new ListNode(0);
  4. ListNode h = head;
  5. while (true) {
  6. boolean isBreak = true;//标记是否遍历完所有链表
  7. int min = Integer.MAX_VALUE;
  8. for (int i = 0; i < lists.length; i++) {
  9. if (lists[i] != null) {
  10. //找出最小下标
  11. if (lists[i].val < min) {
  12. min_index = i;
  13. min = lists[i].val;
  14. }
  15. //存在一个链表不为空,标记改完 false
  16. isBreak = false;
  17. }
  18. }
  19. if (isBreak) {
  20. break;
  21. }
  22. //加到新链表中
  23. ListNode a = new ListNode(lists[min_index].val);
  24. h.next = a;
  25. h = h.next;
  26. //链表后移一个元素
  27. lists[min_index] = lists[min_index].next;
  28. }
  29. h.next = null;
  30. return head.next;
  31. }

时间复杂度:假设最长的链表长度是 n ,那么 while 循环将循环 n 次。假设链表列表里有 k 个链表,for 循环执行 k 次,所以时间复杂度是 O(kn)。

空间复杂度:N 表示最终链表的长度,则为 O(N)。

其实我们不需要创建一个新链表保存,我们只需要改变得到的最小结点的指向就可以了。

  1. public ListNode mergeKLists(ListNode[] lists) {
  2. int min_index = 0;
  3. ListNode head = new ListNode(0);
  4. ListNode h = head;
  5. while (true) {
  6. boolean isBreak = true;
  7. int min = Integer.MAX_VALUE;
  8. for (int i = 0; i < lists.length; i++) {
  9. if (lists[i] != null) {
  10. if (lists[i].val < min) {
  11. min_index = i;
  12. min = lists[i].val;
  13. }
  14. isBreak = false;
  15. }
  16. }
  17. if (isBreak) {
  18. break;
  19. }
  20. //最小的节点接过来
  21. h.next = lists[min_index];
  22. h = h.next;
  23. lists[min_index] = lists[min_index].next;
  24. }
  25. h.next = null;
  26. return head.next;
  27. }

时间复杂度:假设最长的链表长度是 n ,那么 while 循环将循环 n 次。假设链表列表里有 k 个链表,for 循环执行 k 次,所以时间复杂度是 O(kn)。

空间复杂度:O(1)。

解法三 优先队列

解法二中,我们每次都是取出一个最小的,然后加入一个新的, O(1)的复杂度,再找最小的,O(k) 的复杂度。我们完全可以用一个优先队列。

23. Merge k Sorted Lists - 图5

我们将优先级定义为数越小优先级越高,如果用堆实现优先队列,这样我们每次找最小不再需要 O(k),而是 O(log(k)),当然这样的话,我们加入新的话不再是 O(1),也需要 O(log(k))。可以看看这里这里

  1. public ListNode mergeKLists(ListNode[] lists) {
  2. //定义优先队列的比较器
  3. Comparator<ListNode> cmp;
  4. cmp = new Comparator<ListNode>() {
  5. @Override
  6. public int compare(ListNode o1, ListNode o2) {
  7. // TODO Auto-generated method stub
  8. return o1.val-o2.val;
  9. }
  10. };
  11. //建立队列
  12. Queue<ListNode> q = new PriorityQueue<ListNode>(cmp);
  13. for(ListNode l : lists){
  14. if(l!=null){
  15. q.add(l);
  16. }
  17. }
  18. ListNode head = new ListNode(0);
  19. ListNode point = head;
  20. while(!q.isEmpty()){
  21. //出队列
  22. point.next = q.poll();
  23. point = point.next;
  24. //判断当前链表是否为空,不为空就将新元素入队
  25. ListNode next = point.next;
  26. if(next!=null){
  27. q.add(next);
  28. }
  29. }
  30. return head.next;
  31. }

时间复杂度:假如总共有 N 个节点,每个节点入队出队都需要 log(k),所有时间复杂度是 O(N log(k))。

空间复杂度:优先队列需要 O(k)的复杂度。

解法四 两两合并

利用之前合并两个链表的算法,我们直接两两合并,第 0 个和第 1 个链表合并,新生成的再和第 2 个链表合并,新生成的再和第 3 个链表合并…直到全部合并完。

  1. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  2. ListNode h = new ListNode(0);
  3. ListNode ans=h;
  4. while (l1 != null && l2 != null) {
  5. if (l1.val < l2.val) {
  6. h.next = l1;
  7. h = h.next;
  8. l1 = l1.next;
  9. } else {
  10. h.next = l2;
  11. h = h.next;
  12. l2 = l2.next;
  13. }
  14. }
  15. if(l1==null){
  16. h.next=l2;
  17. }
  18. if(l2==null){
  19. h.next=l1;
  20. }
  21. return ans.next;
  22. }
  23. public ListNode mergeKLists(ListNode[] lists) {
  24. if(lists.length==1){
  25. return lists[0];
  26. }
  27. if(lists.length==0){
  28. return null;
  29. }
  30. ListNode head = mergeTwoLists(lists[0],lists[1]);
  31. for (int i = 2; i < lists.length; i++) {
  32. head = mergeTwoLists(head,lists[i]);
  33. }
  34. return head;
  35. }

时间复杂度:不妨假设是 k 个链表并且长度相同,链表总长度为 N,那么第一次合并就是 N/k 和 N/k ,第二次合并就是 2 * N/k 和 N/k,第三次合并就是 3 * N/k 和 N / k,总共进行 n - 1 次合并,每次合并的时间复杂度是 O(n),所以总时间复杂度就是

23. Merge k Sorted Lists - 图6

,可以将两项分开,N/k 其实是常数,分开的第一项是等差数列。

空间复杂度:O(1)。

解法五 两两合并优化

依旧假设是 k 个链表,合并的过程优化下,使得只需要合并 log(k)次。

23. Merge k Sorted Lists - 图7

  1. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  2. ListNode h = new ListNode(0);
  3. ListNode ans=h;
  4. while (l1 != null && l2 != null) {
  5. if (l1.val < l2.val) {
  6. h.next = l1;
  7. h = h.next;
  8. l1 = l1.next;
  9. } else {
  10. h.next = l2;
  11. h = h.next;
  12. l2 = l2.next;
  13. }
  14. }
  15. if(l1==null){
  16. h.next=l2;
  17. }
  18. if(l2==null){
  19. h.next=l1;
  20. }
  21. return ans.next;
  22. }
  23. public ListNode mergeKLists(ListNode[] lists) {
  24. if(lists.length==0){
  25. return null;
  26. }
  27. int interval = 1;
  28. while(interval<lists.length){
  29. System.out.println(lists.length);
  30. for (int i = 0; i + interval< lists.length; i=i+interval*2) {
  31. lists[i]=mergeTwoLists(lists[i],lists[i+interval]);
  32. }
  33. interval*=2;
  34. }
  35. return lists[0];
  36. }

时间复杂度:假设每个链表的长度都是 n ,有 k 个链表,记总结点数是 N = n * k,那么时间复杂度就是

23. Merge k Sorted Lists - 图8

空间复杂度:O(1)。

优先队列的运用印象深刻,此外对两两链表的合并,我们仅仅改变了合并的方式就将时间复杂度降低了很多,美妙!

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情