23. 合并 K 个升序链表

23. 合并 K 个升序链表 - 力扣(LeetCode)

class Solution {
  static ListNode *MergeTwoLists(ListNode *A, ListNode *B) {
    ListNode Dummy;
    ListNode *Prev = &Dummy;

    while (A && B) {
      if (A->val < B->val) {
        Prev->next = A;
        Prev = Prev->next;
        A = A->next;
      } else {
        Prev->next = B;
        Prev = Prev->next;
        B = B->next;
      }
    }
    if (A)
      Prev->next = A;
    if (B)
      Prev->next = B;

    return Dummy.next;
  }

public:
  ListNode* mergeKLists(vector<ListNode*>& lists) {
    if (lists.empty())
      return {};
    return std::reduce(std::next(lists.begin()), lists.end(), lists[0], MergeTwoLists);
  }
};

std::reduce() 的类型检查还挺完备,用 {} 来初始化 init 都不行,必须得用 ListNode * 类型。

暴力解法本身没什么好说的,这个二分和优先队列解法倒是很有意思,可以学学。二分跟暴力相比基本上也是一样的,唯一的就是 controller 部分不太相同:

ListNode* merge(vector <ListNode*> &lists, int l, int r) {
	if (l == r) return lists[l];
	if (l > r) return nullptr;
	int mid = (l + r) >> 1;
	return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
}

ListNode* mergeKLists(vector<ListNode*>& lists) {
	return merge(lists, 0, lists.size() - 1);
}

优先队列解法就更好玩了,我管你这那的先放队列里面,每次就取最小的那个 head,这多好。

class Solution {
  struct ListNodeComparator {
    bool operator()(ListNode *Lhs, ListNode *Rhs) {
      return Lhs->val > Rhs->val;
    }
  };

  priority_queue<ListNode *, vector<ListNode *>, ListNodeComparator> PQ;

public:
  ListNode* mergeKLists(vector<ListNode*>& lists) {
    ListNode Dummy;
    for (auto Head : lists) {
      if (Head)
        PQ.push(Head);
    }

    ListNode *Prev = &Dummy;
    while (!PQ.empty()) {
      ListNode *Smallest = PQ.top();
      PQ.pop();

      Prev->next = Smallest;
      Prev = Prev->next;
      // Keep in mind that we're processing LISTS, not NODES, so we need to
      // manually push tails into the queue.
      if (Smallest->next)
        PQ.push(Smallest->next);
    }
    return Dummy.next;
  }
};

但是注意,这里有坑:在 std::less<>() 下,priority_queue 默认建立的是大根堆!所以你重载的 operator() 得写成大于号而不是小于号才对……

二刷

class Solution {
  ListNode *Merge(ListNode *L1, ListNode *L2) {
    ListNode Dummy(0, L1), *Prev = &Dummy;
    while (L1 && L2) {
      if (L1->val < L2->val) {
        Prev->next = L1;
        L1 = L1->next;
      } else {
        Prev->next = L2;
        L2 = L2->next;
      }
      Prev = Prev->next;
    }
    if (L1)
      Prev->next = L1;
    if (L2)
      Prev->next = L2;
    return Dummy.next;
  }

  ListNode *MergeSort(vector<ListNode *>& V, int L, int R) {
    if (L == R)
      return nullptr;
    if (L + 1 == R)
      return V[L];
 
    int M = L + (R - L) / 2;
    ListNode *Left = MergeSort(V, L, M);
    ListNode *Right = MergeSort(V, M, R);
    // No need to break the chain here, since we're processing VECTOR of nodes,
    // not a whole sequence of a list!
    return Merge(Left, Right);
  }

public:
  ListNode* mergeKLists(vector<ListNode*>& lists) {
    return MergeSort(lists, 0, lists.size());
  }
};

我人傻了,我还想着 Merge() 之前得给链表断一下链,结果发现其实用不着干这个事情,因为题目给我们的是 vector<ListNode *>,不是一整串 ListNode *!从某种程度上来讲,这道题其实还算是比较简单的了,因为如果给你一整串的话……分有序点都得自己分。

class Solution {
  struct Compare {
    bool operator()(const ListNode *Lhs, const ListNode *Rhs) {
      // Keep in mind that priority_queue default to MAXIMUM heap!
      return Lhs->val > Rhs->val;
    }
  };
public:
  ListNode* mergeKLists(vector<ListNode*>& lists) {
    priority_queue<ListNode *, vector<ListNode *>, Compare> PQ;
    // Push heads ONLY, not all elements altogether.
    for (ListNode *Head : lists) {
      // Avoid pushing null heads.
      if (Head)
        PQ.push(Head);
    }

    ListNode Dummy, *Prev = &Dummy;
    while (!PQ.empty()) {
      // Get smallest HEAD, not smallest elements THEMSELVES. We're doing this
      // because we only connect to the first element of each list. we won't
      // connect them altogether.
      ListNode *Smallest = PQ.top();
      PQ.pop();
      // Form the ordered list using Prev.
      Prev->next = Smallest;
      Prev = Prev->next;
      // Consume the smallest element, and push back into the queue. Note that
      // we won't push nullptrs. We don't need to care about the tail since
      // every list in given vector is null-terminated already.
      if (Smallest->next)
        PQ.push(Smallest->next);
    }
    return Dummy.next;
  }
};

这个优先队列解法的关键在于,我们每次要考察的并不是整条列表,而仅仅考虑链表头部那个元素的大小。

事实上在优先队列给予了我们每次挑选最小元素的能力、并且我们确定每个链表都是升序的前提下,我们完全没有必要考虑除头元素之外的其它任何元素。这就是堆排序这种排序方法的关键所在,也是它能实现 \(\mathrm O(kn\log k)\) 的原因。

其实你写归并的时候也是这样,你想想是不是——首先把每个数组分成单元有序,然后组起来的时候也是每次只关注头部。