题目描述
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
解题思路
我一开始的解题思路就是将两个链表一一合并,思路如下:
- list1 + list2 -> list
- list3 + list -> list
- list4 + list -> list
- …
所以第一版的代码如下
public ListNode mergeKLists(ListNode[] lists) { ListNode root = null; if (lists.length == 0) { return root; } root = Arrays.stream(lists).reduce(null, this::mergeTwoList); return root; }
private ListNode mergeTwoList(ListNode l1, ListNode l2) { if (l1 == null) { return l2; } if (l2 == null) { return l1; } ListNode p1 = l1; ListNode p2 = l2; ListNode dummyNode = new ListNode(0); ListNode cuerNode = dummyNode; while (p1 != null && p2 != null) { if (p1.val <= p2.val) { cuerNode.next = p1; cuerNode = cuerNode.next; p1 = p1.next; } else { cuerNode.next = p2; cuerNode = cuerNode.next; p2 = p2.next; } } if (p1 == null) { cuerNode.next = p2; } else { cuerNode.next = p1; } return dummyNode.next; }
|
然后一提交,发现只超过了 24% 的人
说明肯定有更快的算法,我便去找题解,在题解中发现发现使用分治的思路可以更快的得到排序好的数组。由于老弟我归并排序已经写得炉火纯青了,很快得到了第二版的代码
public ListNode mergeKLists(ListNode[] lists) { if (lists.length == 0) { return null; } return mergeKLists(lists, 0 , lists.length - 1); } private ListNode mergeKLists(ListNode[] lists, int left, int right) { if (left == right) { return lists[left]; } ListNode leftList = null; ListNode rightList = null; int mid = left + (right - left) / 2; leftList = mergeKLists(lists, left, mid); rightList = mergeKLists(lists, mid + 1, right); return mergeTwoList(leftList, rightList); }
|
再次提交
已经打败 87% 的人了,时间从 120ms 降到了 2ms,再次感受到了算法的魅力。
在最后我稍微分析归并的方法为什么比一个个合并较快,从合并的次数看,一个个合并需要合并 $n - 1$ 次,而归并排序需要合并
$$
\frac{n}{2} + \frac{n}{4} + \frac{n}{8} + \dots + 1
$$
为了方便计算,不妨假设 $n = 2^i$,即 $n$ 为 $2$ 的整数次幂,则上次可以改为
$$
\sum_{i = 1}^{\log_2 n} \frac{n}{2^i} \approx n - 1
$$
所以从合并的次数上看,几乎是相当的。但是注意到一一合并得到的链表越来越长
$$
\sum_{i=1}^{\log _{2} n} \frac{n}{2^{i}} \approx n-1
$$
- list + listi -> list
- list + list(i + 1) -> list
- …
链表 list 越来越长,合并需要的时间就越多。但是对于归并合并,它先将链表数组划分为两个一组然后合并,这两个一组的链表都是较短的,所需合并的速度较快。所以虽然他们合并的次数相差不大,但是归并合并的链表较一一合并短很多,所以便会较快。
参考链接