在面试中遇到了这道题:如何实现多个升序链表的合并。这是 LeetCode 上的一道原题,题目具体如下:

用归并实现合并 K 个升序链表

LeetCode 23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

  1. 输入:lists = [[1,4,5],[1,3,4],[2,6]]
  2. 输出:[1,1,2,3,4,4,5,6]
  3. 解释:链表数组如下:
  4. [
  5. 1->4->5,
  6. 1->3->4,
  7. 2->6
  8. ]
  9. 将它们合并到一个有序链表中得到。
  10. 1->1->2->3->4->4->5->6

这题可以用归并的思想来实现,我们两两链表合并,到最后合成所有的链表。代码如下:

  1. public ListNode mergeKLists(ListNode[] lists) {
  2. return merge(lists, 0, lists.length - 1);
  3. }
  4. public ListNode merge(ListNode[] lists, int left, int right) {
  5. if(left == right) {
  6. return lists[left];
  7. }
  8. if (left > right) {
  9. return null;
  10. }
  11. int mid = (left + right) >> 1;
  12. return mergeTwoLists(merge(lists, left, mid), merge(lists, mid + 1, right));
  13. }
  14. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  15. if(l1 == null) {
  16. return l2;
  17. }
  18. if(l2 == null) {
  19. return l1;
  20. }
  21. ListNode head = new ListNode(-1);
  22. ListNode cur = head;
  23. while(l1 != null && l2 != null) {
  24. if(l1.val < l2.val) {
  25. cur.next = l1;
  26. l1 = l1.next;
  27. }else {
  28. cur.next = l2;
  29. l2 = l2.next;
  30. }
  31. cur = cur.next;
  32. }
  33. cur.next = l1 == null ? l2:l1;
  34. return head.next;
  35. }

现在我们来回顾一下归并排序的知识

一、归并排序

1. 归并排序的定义

  • 基本思路:借助外部空间,合并两个有序数组/序列,得到更长的数组
  • 算法思想:分而治之

比如对于数组[8,4,5,7,1,3,6,2]的排序:整体的过程是这样:先“分”成小问题,再进行“治”操作

2.归并排序算法代码实现

先来看看归并排序实现一个数组[8,4,5,7,1,3,6,2]的排序,难以理解的是合并相邻有序子序列这块,我们来看 [4,5,7,8] 和[1,2,3,6]这两个已经有序的子序列的合并:图片转自这篇博客图解排序算法(四)之归并排序

  1. public int[] sortArray(int[] nums) {
  2. int[] temp = new int[nums.length];
  3. merge(nums, 0, nums.length - 1, temp);
  4. return nums;
  5. }
  6. public void merge(int[] nums, int left, int right, int[] temp) {
  7. if(left < right) {
  8. int mid = (left + right) >> 1;
  9. merge(nums, left, mid, temp);
  10. merge(nums, mid + 1, right, temp);
  11. mergeSort(nums, left, mid, right, temp);
  12. }
  13. }
  14. public void mergeSort(int[] nums, int left, int mid, int right, int[] temp) {
  15. int i = left;
  16. int j = mid + 1;
  17. for(int k = left; k <= right; k++) {
  18. temp[k] = nums[k];
  19. }
  20. for(int k = left; k <= right; k++) {
  21. //当 i 指针走完时,将 j 指针部分复制到数组中
  22. if(i == mid +1) {
  23. nums[k] = temp[j];
  24. j++;
  25. //若 j 指针走完,将 i 指针部分复制到最后数组中
  26. }else if(j == right + 1) {
  27. nums[k] = temp[i];
  28. i++;
  29. //这里的 = 是保持排序算法的稳定性,即排序后相等的数据原有顺序不变
  30. }else if(temp[i] <= temp[j]) {
  31. nums[k] = temp[i];
  32. i++;
  33. }else {
  34. nums[k] = temp[j];
  35. j++;
  36. }
  37. }
  38. }

二、归并排序的一些经典题

1.LeetCode 88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3

输出:[1,2,2,3,5,6]

解释:需要合并 [1,2,3] 和 [2,5,6] 。

合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

这道题可以用归并排序的思想来完成,这里就是用的“治”操作:

  1. public void merge(int[] nums1, int m, int[] nums2, int n) {
  2. int[] temp = new int[m+n];
  3. for(int t = 0; t < m; t++) {
  4. temp[t] = nums1[t];
  5. }
  6. for(int t = m, g = 0; t < m + n & g < n; t++,g++) {
  7. temp[t] = nums2[g];
  8. }
  9. int i = 0;
  10. int j = m;
  11. for(int k = 0; k < m + n; k++) {
  12. if(i == m) {
  13. nums1[k] = temp[j];
  14. j++;
  15. } else if(j == m + n) {
  16. nums1[k] = temp[i];
  17. i++;
  18. } else if(temp[i] <= temp[j]) {
  19. nums1[k] = temp[i];
  20. i++;
  21. } else {
  22. nums1[k] = temp[j];
  23. j++;
  24. }
  25. }
  26. }

2.剑指 Offer 51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]

输出: 5

这题实际上还是利用数组中的元素两两配对比较,也就是分治处理,不过这次是比较大小后的计数。

  1. public int reversePairs(int[] nums) {
  2. int len = nums.length;
  3. if(len < 2) {
  4. return 0;
  5. }
  6. int[] temp = new int[len];
  7. return mergePairs(nums, 0, len - 1, temp);
  8. }
  9. public int mergePairs(int[] nums, int left, int right, int[] temp) {
  10. //如果是无参void 则可写成 return; 或者不写
  11. if(left >= right) {
  12. return 0;
  13. }
  14. int mid = (left + right) >> 1;
  15. int leftCount = mergePairs(nums, left, mid, temp);
  16. int rightCount = mergePairs(nums, mid + 1, right, temp);
  17. int reverseCount = merge(nums, left, mid, right, temp);
  18. //最后记得返回三者之和
  19. return leftCount + rightCount + reverseCount;
  20. }
  21. public int merge(int[] nums, int left, int mid, int right, int[] temp) {
  22. int i = left;
  23. int j = mid + 1;
  24. int count = 0;
  25. for(int t = left; t <= right; t++) {
  26. temp[t] = nums[t];
  27. }
  28. for(int k = left; k <= right; k++) {
  29. if(i == mid + 1) {
  30. nums[k] = temp[j++];
  31. } else if(j == right + 1 || temp[i] <= temp[j]) {
  32. nums[k] = temp[i++];
  33. } else {
  34. nums[k] = temp[j++];
  35. count += mid - i + 1;
  36. }
  37. }
  38. return count;
  39. }

这里要说一下逆序数的求法:前提是两个序列有序

如果有两个有序序列:

Seq1:3 4 5

Seq2:2 6 8 9

对于序列seq1中的某个数a[i],序列seq2中的某个数a[j]:

  • 如果a[i]<a[j],没有逆序数
  • 如果a[i]>a[j],那么逆序数为seq1 中a[i]后边元素的个数(包括a[i]),即len1 -i+1。

3.LeetCode 315. 计算右侧小于当前元素的个数

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例 1:

  1. 输入:nums = [5,2,6,1]
  2. 输出:[2,1,1,0]
  3. 解释:
  4. 5 的右侧有 2 个更小的元素 (2 1)
  5. 2 的右侧仅有 1 个更小的元素 (1)
  6. 6 的右侧有 1 个更小的元素 (1)
  7. 1 的右侧有 0 个更小的元素

这题和第二题类似,但是这里要解决定位的问题,因为我们的元素节点在归并排序的时候是会移动的,所以需要设置一个索引数组来给这些元素定位。但是求逆序数用的是第二种方法:在前有序数组出列时,计算后有序数组中已经出列的元素个数。

  1. public List<Integer> countSmaller(int[] nums) {
  2. int len = nums.length;
  3. List<Integer> res = new ArrayList<>();
  4. if(len < 2) {
  5. res.add(0);
  6. return res;
  7. }
  8. int[] temp = new int[len];
  9. int[] indexes = new int[len];
  10. int[] result = new int[len];
  11. for(int i = 0; i < len; i++) {
  12. indexes[i] = i;
  13. }
  14. merge(nums, 0, len - 1, temp, indexes, result);
  15. for(int i = 0; i < len; i++) {
  16. res.add(result[i]);
  17. }
  18. return res;
  19. }
  20. public void merge(int[] nums, int left, int right, int[] temp, int[] indexes, int[] result) {
  21. if(left >= right) {
  22. return;
  23. }
  24. int mid = (left + right) >> 1;
  25. merge(nums,left, mid, temp,indexes,result);
  26. merge(nums,mid+1,right,temp,indexes,result);
  27. mergeSort(nums,left,right,mid,temp,indexes,result);
  28. }
  29. public void mergeSort(int[] nums, int left, int right, int mid, int[] temp, int[] indexes, int[] result) {
  30. int i = left;
  31. int j = mid + 1;
  32. for(int t = left; t <= right; t++) {
  33. temp[t] = indexes[t];
  34. }
  35. for(int k = left; k <= right; k++) {
  36. if(i == mid + 1) {
  37. indexes[k] = temp[j++];
  38. } else if(j == right + 1) {
  39. indexes[k] = temp[i++];
  40. result[indexes[k]] += right - mid;
  41. } else if(nums[temp[i]] <= nums[temp[j]]) {
  42. indexes[k] = temp[i++];
  43. result[indexes[k]] += j - mid - 1;
  44. } else {
  45. indexes[k] = temp[j++];
  46. }
  47. }
  48. }

归并排序的思想很重要,在解决负责问题的分治思想有利于将大问题分解。从而更快的解决问题。

参考资料

https://www.cnblogs.com/chengxiao/p/6194356.html

https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/gui-bing-pai-xu-suo-yin-shu-zu-python-dai-ma-java-/

浅谈归并排序:合并 K 个升序链表的归并解法的更多相关文章

  1. [LeetCode题解]23. 合并K个升序链表 | 分治 + 递归

    方法一:分治 + 递归 解题思路 在21. 合并两个有序链表,我们知道如何合并两个有序链表.而本题是合并 k 个有序链表,可以通过大问题拆分成小问题解决,即把 k 个链表,拆分成 k/2 个链表组,俩 ...

  2. 【LeetCode】23. Merge k Sorted Lists 合并K个升序链表

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:合并,链表,单链表,题解,leetcode, 力扣,Py ...

  3. LeetCode-023-合并K个升序链表

    合并K个升序链表 题目描述:给你一个链表数组,每个链表都已经按升序排列. 请你将所有链表合并到一个升序链表中,返回合并后的链表. 示例说明请见LeetCode官网. 来源:力扣(LeetCode) 链 ...

  4. leetcode python 012 hard 合并k个有序链表

    #[LeetCode] Merge k Sorted Lists 合并k个有序链表(升序) import numpy as npimport time class Node(object):    d ...

  5. 23.合并k个有序链表

    合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂度. 示例: 输入: [   1->4->5,   1->3->4,   2->6 ] 输出: 1-&g ...

  6. [LeetCode] Merge k Sorted Lists 合并k个有序链表

    Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 这 ...

  7. [LeetCode] 23. 合并K个排序链表

    题目链接: https://leetcode-cn.com/problems/merge-k-sorted-lists/ 题目描述: 合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂 ...

  8. [Swift]LeetCode23. 合并K个排序链表 | Merge k Sorted Lists

    Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. E ...

  9. 合并K个排序链表

    合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂度. 示例: 输入: [   1->4->5,   1->3->4,   2->6 ] 输出: 1-&g ...

随机推荐

  1. vue中使用两个window.onresize问题解决

    在vue开发中,因为引用的父组件和子组件都使用了window.onresize以至于一个window.onresize失效.找了下解决方案,可以采用下面的方式写就可以了. window.onresiz ...

  2. 05.python解析式与生成器表达式

    解析式和生成器表达式 列表解析式 列表解析式List Comprehension,也叫列表推导式 #生成一个列表,元素0-9,将每个元素加1后的平方值组成新的列表 x = [] for i in ra ...

  3. Typora+PicGo-Core实现图片自动上传gitee图床

    说明: 使用gitee作为图床: 客户机为Mac M1: Typora版本:1.0.2 (5990). gitee配置步骤 需要拥有一个gitee账号,创建一个公有仓库用于存储图片,然后需要生成一个t ...

  4. JAVA-JDK1.7-ConCurrentHashMap-源码并且debug说明

    概述 在一个程序员的成长过程就一定要阅读源码,并且了解其中的原理,只有这样才可以深入了解其中的功能,就像ConCurrentHashMap 是线程安全的,到底是如何安全的?以及如何正确使用它?reha ...

  5. ToDesk-----个人免费 极致流畅的远程协助软件

    ToDesk https://www.todesk.com/ ToDesk官方下载地址 https://www.todesk.com/ 还支持文件传输,用过许多远程的控制工具,这个自我感觉比向日葵好用 ...

  6. python极简教程05:生成器和匿名函数

    测试奇谭,BUG不见. 讲解之前,我先说说我的教程和网上其他教程的区别: 1 我分享的是我在工作中高频使用的场景,是精华内容: 2 我分享的是学习方法,亦或说,是指明你该学哪些.该重点掌握哪些内容: ...

  7. Visaul Studio 2015 MFC控件使用之--按钮(Button)

    在MFC开发当中,比较常用的控件之一便是Button控件了,该控件的除了可以通过点击产生的开关量当作开关来使用,还可以设置其颜色变化当作显示灯,按钮控件的使用相对来比较简单. 打开工程解决方案的资源视 ...

  8. 【刷题-LeetCode】166 Fraction to Recurring Decimal

    Fraction to Recurring Decimal Given two integers representing the numerator and denominator of a fra ...

  9. 集合框架-工具类-JDK5.0特性-静态导入

    1 package cn.itcast.p4.news.demo; 2 3 import java.util.ArrayList; 4 //import java.util.Collections; ...

  10. Django settings.py配置文件

    import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 这里用到了python中一个神奇的变量 file 这个变量可以获取到当前 ...