Leetcode 23. 合并K个排序链表

题目

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6

解答

解法一:逐个遍历解法 O(nk)

思路很简单,看主函数就行了。

如果 lists 不是空,就弹出一个最小的结点。

时间复杂度是最高的:O(nk) 的时间, O(1) 的空间

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode root = new ListNode(0);
        ListNode cursor = root;
        
        while(!isEmpty(lists)) {
            // 选择一个最小的结点
            cursor.next = popMin(lists);
            cursor = cursor.next;
        }
        
        return root.next;
    }
    
    private boolean isEmpty(ListNode[] lists) {
        for(int i = 0; i < lists.length; i ++) {
            if(lists[i] != null) return false;                    
        }
        
        return true;
    }
    
    private ListNode popMin(ListNode[] lists) {
        ListNode min = null;
        int index = -1;
        for(int i = 0; i < lists.length; i ++) {
            if(lists[i] == null) continue;
            
            if(min == null || lists[i].val < min.val) {
                min = lists[i];
                index = i;
            }
        }
        
        lists[index] = min.next;
        return min;
    }
}


结果

在这里插入图片描述

解法二:暴力排序解法 O(nlogn)

具体步骤:

  1. 首先将所有链表的数据提取出来,放入到一个 List 容器中。

  2. 对 List 容器的内容自然排序(即升序),时间复杂度 O(nlogn)

  3. 对已经排序的 List 容器内的数据重新组装成链表结构

复杂度:O(nlogn) 的时间, O(n) 的空间

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        List<Integer> datas = new ArrayList<>();
        // 先采集数据
        for(int i = 0; i < lists.length; i ++) {
            ListNode cur = lists[i];
            while(cur != null) {
                datas.add(cur.val);
                cur = cur.next;
            }
        }
        
        // 自然排序(升序)
        Collections.sort(datas);
       	
        // 重新组装链表
        ListNode root = new ListNode(0);
        ListNode cursor = root;
        for(int i = 0; i < datas.size(); i ++) {
            cursor.next = new ListNode(datas.get(i));
            cursor = cursor.next;
        }
        
        return root.next;
    }
}

结果

在这里插入图片描述

解法三:小顶堆解法 O(nlogk)

可以使用实现了 数据结构的集合。(TreeSet 或者 PriorityQueue)

具体流程:

  1. 比较器包含下述规则:
    1. 索引相等的覆盖掉。
    2. 索引不同且链表头部相同,就比较索引值。
    3. 否则比较链表的头部大小。
  2. 根据这个比较器创建一个优先队列,然后将所有的链表加入优先队列。
  3. 每次从优先队列内拿出一个最小结点,处理完成后将新的链表再次放入优先队列。
  4. 不断循环,直到优先队列内没有了链表。
  5. 最后返回 root.next 获得真实的头部结点 。

复杂度:O(nlogk) 的时间, O(n) 的空间

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    class Couple {
        int index;
        ListNode e;
        Couple(int index, ListNode e) {
            this.index = index;
            this.e = e;
        }
    }
    
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<Couple> queue = new PriorityQueue<>(new Comparator<Couple>() {
            @Override
            public int compare(Couple o1, Couple o2) {
                if(o1.index == o2.index) return 0;
                
                if(o1.e.val == o2.e.val) {
                    return o1.index - o2.index;
                }

                return o1.e.val - o2.e.val;
            }
        }); 
        
        for(int i = 0; i < lists.length; i ++) {
            if(lists[i] != null) {
                queue.offer(new Couple(i, lists[i])); 
            }
        }
        
        ListNode root = new ListNode(0);
        ListNode cursor = root;
        while(!queue.isEmpty()) {
            Couple node = queue.poll();
            cursor.next = node.e;
            cursor = cursor.next;
            if(node.e.next != null) {
                queue.offer(new Couple(node.index, node.e.next));
            }
        }
        
        return root.next;
    }
}

结果

在这里插入图片描述

解法四:分治解法 O(nlogk)

强力推荐,很精妙的分治,有点希尔排序的味道。

核心要点:

  1. step 的变化情况?
  2. 融合哪两个?
  3. i 的变化情况( i += 2 * step )?

具体流程:

  1. 第一次:对 lists 总链表两两融合,lists 有效链表个数变为总链表的 1/2
  2. 第二次:对 lists 的这 1/2 个有效链表两两融合,lists 有效链表个数变为总链表的 1/4
  3. 第三次:对 lists 的这 1/4 个有效链表两两融合,lists 有效链表个数变为总链表的 1/8
  4. … …
  5. 第logk次: lists只有一个有效链表,它融合了全部链表。
  6. 返回 lists[0] 即为新链表。

合并两个有序链表可以看我上篇博客:https://blog.csdn.net/LetJava/article/details/95902521

复杂度:O(nlogk) 的时间, O(1) 的空间

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists == null || lists.length == 0) return null;
        
        int n = lists.length;
        for(int step = 1; step < n; step *= 2) {
            for(int i = 0; i + step < n; i += 2 * step) {
                lists[i] = mergeTwoLists(lists[i], lists[i + step]);
            }
        }
        
        return lists[0];
    }
    
    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode root = new ListNode(0);
        ListNode cursor = root;
        while(l1 != null && l2 != null) {
            if(l1.val < l2.val) {
                cursor.next = l1;
                cursor = l1;
                l1 = l1.next;
            } else {
                cursor.next = l2;
                cursor = l2;
                l2 = l2.next;
            }
        }
        
        if(l1 != null) cursor.next = l1;
        if(l2 != null) cursor.next = l2;
        
        return root.next;
    }
}

结果

在这里插入图片描述

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页