首页 > 编程语言 > 详细

leetcode题解之合并K个排序链表

时间:2020-06-11 12:38:32      阅读:44      评论:0      收藏:0      [点我收藏+]

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

示例:

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

?? 视频题解

code:

vid:

uuid:

requestId:

播放时间:

提示信息

技术分享图片
字幕
    倍速
      清晰度
        音轨
          倍速正常
          字幕Off
          音轨
          清晰度

          ?? 文字题解

          前置知识:合并两个有序链表

          思路
          在解决「合并K个排序链表」这个问题之前,我们先来看一个更简单的问题:如何合并两个有序链表?假设链表 aabb 的长度都是 nn如何在 O(n)O(n) 的时间代价以及 O(1)O(1) 的空间代价完成合并? 这个问题在面试中常常出现,为了达到空间代价是 O(1)O(1),我们的宗旨是「原地调整链表元素的 next 指针完成合并」。以下是合并的步骤和注意事项,对这个问题比较熟悉的读者可以跳过这一部分。此部分建议结合代码阅读。

          • 首先我们需要一个变量 head 来保存合并之后链表的头部,你可以把 head 设置为一个虚拟的头(也就是 headval 属性不保存任何值),这是为了方便代码的书写,在整个链表合并完之后,返回它的下一位置即可。
          • 我们需要一个指针 tail 来记录下一个插入位置的前一个位置,以及两个指针 aPtrbPtr 来记录 aabb 未合并部分的第一位。注意这里的描述,tail 不是下一个插入的位置,aPtrbPtr 所指向的元素处于「待合并」的状态,也就是说它们还没有合并入最终的链表。 当然你也可以给他们赋予其他的定义,但是定义不同实现就会不同。
          • aPtrbPtr 都不为空的时候,取 val 熟悉较小的合并;如果 aPtr 为空,则把整个 bPtr 以及后面的元素全部合并;bPtr 为空时同理。
          • 在合并的时候,应该先调整 tailnext 属性,再后移 tail*PtraPtr 或者 bPtr)。那么这里 tail*Ptr 是否存在先后顺序呢?它们谁先动谁后动都是一样的,不会改变任何元素的 next 指针。

          代码

          ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
              if ((!a) || (!b)) return a ? a : b;
              ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
              while (aPtr && bPtr) {
                  if (aPtr->val < bPtr->val) {
                      tail->next = aPtr; aPtr = aPtr->next;
                  } else {
                      tail->next = bPtr; bPtr = bPtr->next;
                  }
                  tail = tail->next;
              }
              tail->next = (aPtr ? aPtr : bPtr);
              return head.next;
          }
          

          复杂度

          • 时间复杂度:O(n)O(n)
          • 空间复杂度:O(1)O(1)

          方法一:顺序合并

          思路

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

          代码

          class Solution {
          public:
              ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
                  if ((!a) || (!b)) return a ? a : b;
                  ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
                  while (aPtr && bPtr) {
                      if (aPtr->val < bPtr->val) {
                          tail->next = aPtr; aPtr = aPtr->next;
                      } else {
                          tail->next = bPtr; bPtr = bPtr->next;
                      }
                      tail = tail->next;
                  }
                  tail->next = (aPtr ? aPtr : bPtr);
                  return head.next;
              }
          
          <span class="hljs-function">ListNode* <span class="hljs-title">mergeKLists</span><span class="hljs-params">(<span class="hljs-built_in">vector</span>&lt;ListNode*&gt;&amp; lists)</span> </span>{
              ListNode *ans = <span class="hljs-literal">nullptr</span>;
              <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>; i &lt; lists.size(); ++i) {
                  ans = mergeTwoLists(ans, lists[i]);
              }
              <span class="hljs-keyword">return</span> ans;
          }
          

          };

          复杂度

          • 时间复杂度:假设每个链表的最长长度是 nn。在第一次合并后,ans 的长度为 nn;第二次合并后,ans 的长度为 2×n2\times n,第 ii 次合并后,ans 的长度为 i×ni\times n。第 ii 次合并的时间代价是 O(n+(i?1)×n)=O(i×n)O(n + (i - 1) \times n) = O(i \times n),那么总的时间代价为 O(i=1k(i×n))=O((1+k)?k2×n)=O(k2n)O(\sum_{i = 1}^{k} (i \times n)) = O(\frac{(1 + k)\cdot k}{2} \times n) = O(k^2 n),故渐进时间复杂度为 O(k2n)O(k^2 n)
          • 空间复杂度:没有用到与 kknn 规模相关的辅助空间,故渐进空间复杂度为 O(1)O(1)

          方法二:分治合并

          思路

          考虑优化方法一,用分治的方法进行合并。

          • kk 个链表配对并将同一对中的链表合并;
          • 第一轮合并以后, kk 个链表被合并成了 k2\frac{k}{2} 个链表,平均长度为 2nk\frac{2n}{k},然后是 k4\frac{k}{4} 个链表, k8\frac{k}{8} 个链表等等;
          • 重复这一过程,直到我们得到了最终的有序链表。

          技术分享图片

          代码

          class Solution {
          public:
              ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
                  if ((!a) || (!b)) return a ? a : b;
                  ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
                  while (aPtr && bPtr) {
                      if (aPtr->val < bPtr->val) {
                          tail->next = aPtr; aPtr = aPtr->next;
                      } else {
                          tail->next = bPtr; bPtr = bPtr->next;
                      }
                      tail = tail->next;
                  }
                  tail->next = (aPtr ? aPtr : bPtr);
                  return head.next;
              }
          
          <span class="hljs-function">ListNode* <span class="hljs-title">merge</span><span class="hljs-params">(<span class="hljs-built_in">vector</span> &lt;ListNode*&gt; &amp;lists, <span class="hljs-keyword">int</span> l, <span class="hljs-keyword">int</span> r)</span> </span>{
              <span class="hljs-keyword">if</span> (l == r) <span class="hljs-keyword">return</span> lists[l];
              <span class="hljs-keyword">if</span> (l &gt; r) <span class="hljs-keyword">return</span> <span class="hljs-literal">nullptr</span>;
              <span class="hljs-keyword">int</span> mid = (l + r) &gt;&gt; <span class="hljs-number">1</span>;
              <span class="hljs-keyword">return</span> mergeTwoLists(merge(lists, l, mid), merge(lists, mid + <span class="hljs-number">1</span>, r));
          }
          
          <span class="hljs-function">ListNode* <span class="hljs-title">mergeKLists</span><span class="hljs-params">(<span class="hljs-built_in">vector</span>&lt;ListNode*&gt;&amp; lists)</span> </span>{
              <span class="hljs-keyword">return</span> merge(lists, <span class="hljs-number">0</span>, lists.size() - <span class="hljs-number">1</span>);
          }
          

          };

          复杂度

          • 时间复杂度:考虑递归「向上回升」的过程——第一轮合并 k2\frac{k}{2} 组链表,每一组的时间代价是 O(2n)O(2n);第二轮合并 k4\frac{k}{4} 组链表,每一组的时间代价是 O(4n)O(4n)......所以总的时间代价是 O(i=1k2i×2in)=O(kn×log?k)O(\sum_{i = 1}^{\infty} \frac{k}{2^i} \times 2^i n) = O(kn \times \log k),故渐进时间复杂度为 O(kn×log?k)O(kn \times \log k)
          • 空间复杂度:递归会使用到 O(log?k)O(\log k) 空间代价的栈空间。

          方法三:使用优先队列合并

          思路

          这个方法和前两种方法的思路有所不同,我们需要维护当前每个链表没有被合并的元素的最前面一个,kk 个链表就最多有 kk 个满足这样条件的元素,每次在这些元素里面选取 val 属性最小的元素合并到答案中。在选取最小元素的时候,我们可以用优先队列来优化这个过程。

          代码

          class Solution {
          public:
              struct Status {
                  int val;
                  ListNode *ptr;
                  bool operator < (const Status &rhs) const {
                      return val > rhs.val;
                  }
              };
          
          priority_queue &lt;Status&gt; q;
          
          <span class="hljs-function">ListNode* <span class="hljs-title">mergeKLists</span><span class="hljs-params">(<span class="hljs-built_in">vector</span>&lt;ListNode*&gt;&amp; lists)</span> </span>{
              <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> node: lists) {
                  <span class="hljs-keyword">if</span> (node) q.push({node-&gt;val, node});
              }
              ListNode head, *tail = &amp;head;
              <span class="hljs-keyword">while</span> (!q.empty()) {
                  <span class="hljs-keyword">auto</span> f = q.top(); q.pop();
                  tail-&gt;next = f.ptr; 
                  tail = tail-&gt;next;
                  <span class="hljs-keyword">if</span> (f.ptr-&gt;next) q.push({f.ptr-&gt;next-&gt;val, f.ptr-&gt;next});
              }
              <span class="hljs-keyword">return</span> head.next;
          }
          

          };

          复杂度

          • 时间复杂度:考虑优先队列中的元素不超过 kk 个,那么插入和删除的时间代价为 O(log?k)O(\log k),这里最多有 knkn 个点,对于每个点都被插入删除各一次,故总的时间代价即渐进时间复杂度为 O(kn×log?k)O(kn \times \log k)
          • 空间复杂度:这里用了优先队列,优先队列中的元素不超过 kk 个,故渐进空间复杂度为 O(k)O(k)

          leetcode题解之合并K个排序链表

          原文:https://www.cnblogs.com/leetcodetijie/p/13091769.html

          (0)
          (0)
             
          举报
          评论 一句话评论(0
          分享档案
          最新文章
          教程昨日排行
          关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
          © 2014 bubuko.com 版权所有
          打开技术之扣,分享程序人生!