Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.node
合併k個排好的的單鏈表。分析和描述它的複雜性。算法
使用小頂堆來實現,先將K個鏈表的頭結點入堆,取堆頂元素,這個結點就是最小的,接着讓取出的這個結點的下一個結點入堆,再取堆頂元素,其爲第二小的結點,一直這樣子操做,直到全部的結點都處理完,這樣就完成了鏈表的合併,更多細節請見代碼和註釋。假設K個鏈表一共有N個結點,時間複雜度是O(N*log(K)),空間複雜度是O(K)。數組
結點類ide
public class ListNode { int val; ListNode next; ListNode(int x) { val = x; } }
算法實現類this
public class Solution { public ListNode mergeKLists(ListNode[] lists) { // 爲空或者沒有元素 if (lists == null || lists.length < 1) { return null; } // 只有一個元素 if (lists.length == 1) { return lists[0]; } // 建立一個小頂堆,而且使用一個匿名內部類做爲比較器 MinHeap<ListNode> minHeap = new MinHeap<ListNode>(new Comparator<ListNode>() { @Override public int compare(ListNode o1, ListNode o2) { if (o1 == null) { return -1; } if (o2 == null) { return 1; } return o1.val - o2.val; } }); // 將數組中鏈表的第一個結點入堆 for (ListNode node : lists) { if (node != null) { minHeap.add(node); } } // 頭結點,做輔助使用 ListNode head = new ListNode(0); // 當前處理的結點 ListNode curr = head; while (!minHeap.isEmpty()) { ListNode node = minHeap.deleteTop(); // 結點的下一個結點不爲空就將下一個結點入堆 if (node.next != null) { minHeap.add(node.next); } curr.next = node; curr = node; } return head.next; } /** * 小頂堆 * * @param <T> */ private static class MinHeap<T> { // 堆中元素存放的集合 private List<T> items; private Comparator<T> comp; /** * 構造一個椎,始大小是32 */ public MinHeap(Comparator<T> comp) { this(32, comp); } /** * 造詣一個指定初始大小的堆 * * @param size 初始大小 */ public MinHeap(int size, Comparator<T> comp) { items = new ArrayList<>(size); this.comp = comp; } /** * 向上調整堆 * * @param index 被上移元素的起始位置 */ public void siftUp(int index) { T intent = items.get(index); // 獲取開始調整的元素對象 while (index > 0) { // 若是不是根元素 int parentIndex = (index - 1) / 2; // 找父元素對象的位置 T parent = items.get(parentIndex); // 獲取父元素對象 if (comp.compare(intent, parent) < 0) { //上移的條件,子節點比父節點小 items.set(index, parent); // 將父節點向下放 index = parentIndex; // 記錄父節點下放的位置 } else { // 子節點不比父節點小,說明父子路徑已經按從小到大排好順序了,不須要調整了 break; } } // index此時記錄是的最後一個被下放的父節點的位置(也多是自身), // 因此將最開始的調整的元素值放入index位置便可 items.set(index, intent); } /** * 向下調整堆 * * @param index 被下移的元素的起始位置 */ public void siftDown(int index) { T intent = items.get(index); // 獲取開始調整的元素對象 int leftIndex = 2 * index + 1; // // 獲取開始調整的元素對象的左子結點的元素位置 while (leftIndex < items.size()) { // 若是有左子結點 T minChild = items.get(leftIndex); // 取左子結點的元素對象,而且假定其爲兩個子結點中最小的 int minIndex = leftIndex; // 兩個子節點中最小節點元素的位置,假定開始時爲左子結點的位置 int rightIndex = leftIndex + 1; // 獲取右子結點的位置 if (rightIndex < items.size()) { // 若是有右子結點 T rightChild = items.get(rightIndex); // 獲取右子結點的元素對象 if (comp.compare(rightChild, minChild) < 0) { // 找出兩個子節點中的最小子結點 minChild = rightChild; minIndex = rightIndex; } } // 若是最小子節點比父節點小,則須要向下調整 if (comp.compare(minChild, intent) < 0) { items.set(index, minChild); // 將子節點向上移 index = minIndex; // 記錄上移節點的位置 leftIndex = index * 2 + 1; // 找到上移節點的左子節點的位置 } else { // 最小子節點不比父節點小,說明父子路徑已經按從小到大排好順序了,不須要調整了 break; } } // index此時記錄是的最後一個被上移的子節點的位置(也多是自身), // 因此將最開始的調整的元素值放入index位置便可 items.set(index, intent); } /** * 向堆中添加一個元素 * * @param item 等待添加的元素 */ public void add(T item) { items.add(item); // 將元素添加到最後 siftUp(items.size() - 1); // 循環上移,以完成重構 } /** * 刪除堆頂元素 * * @return 堆頂部的元素 */ public T deleteTop() { if (items.isEmpty()) { // 若是堆已經爲空,就報出異常 throw new RuntimeException("The heap is empty."); } T maxItem = items.get(0); // 獲取堆頂元素 T lastItem = items.remove(items.size() - 1); // 刪除最後一個元素 if (items.isEmpty()) { // 刪除元素後,若是堆爲空的狀況,說明刪除的元素也是堆頂元素 return lastItem; } items.set(0, lastItem); // 將刪除的元素放入堆頂 siftDown(0); // 自上向下調整堆 return maxItem; // 返回堆頂元素 } /** * 判斷堆是否爲空 * * @return true是空,false否 */ public boolean isEmpty() { return items.isEmpty(); } } }