浅谈归并排序:合并 K 个升序链表的归并解法
在面试中遇到了这道题:如何实现多个升序链表的合并。这是 LeetCode 上的一道原题,题目具体如下:
用归并实现合并 K 个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
这题可以用归并的思想来实现,我们两两链表合并,到最后合成所有的链表。代码如下:
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists, 0, lists.length - 1);
}
public ListNode merge(ListNode[] lists, int left, int right) {
if(left == right) {
return lists[left];
}
if (left > right) {
return null;
}
int mid = (left + right) >> 1;
return mergeTwoLists(merge(lists, left, mid), merge(lists, mid + 1, right));
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) {
return l2;
}
if(l2 == null) {
return l1;
}
ListNode head = new ListNode(-1);
ListNode cur = head;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
}else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 == null ? l2:l1;
return head.next;
}
现在我们来回顾一下归并排序的知识
一、归并排序
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]这两个已经有序的子序列的合并:图片转自这篇博客图解排序算法(四)之归并排序


public int[] sortArray(int[] nums) {
int[] temp = new int[nums.length];
merge(nums, 0, nums.length - 1, temp);
return nums;
}
public void merge(int[] nums, int left, int right, int[] temp) {
if(left < right) {
int mid = (left + right) >> 1;
merge(nums, left, mid, temp);
merge(nums, mid + 1, right, temp);
mergeSort(nums, left, mid, right, temp);
}
}
public void mergeSort(int[] nums, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
for(int k = left; k <= right; k++) {
temp[k] = nums[k];
}
for(int k = left; k <= right; k++) {
//当 i 指针走完时,将 j 指针部分复制到数组中
if(i == mid +1) {
nums[k] = temp[j];
j++;
//若 j 指针走完,将 i 指针部分复制到最后数组中
}else if(j == right + 1) {
nums[k] = temp[i];
i++;
//这里的 = 是保持排序算法的稳定性,即排序后相等的数据原有顺序不变
}else if(temp[i] <= temp[j]) {
nums[k] = temp[i];
i++;
}else {
nums[k] = temp[j];
j++;
}
}
}
二、归并排序的一些经典题
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 中的元素。
这道题可以用归并排序的思想来完成,这里就是用的“治”操作:
public void merge(int[] nums1, int m, int[] nums2, int n) {
int[] temp = new int[m+n];
for(int t = 0; t < m; t++) {
temp[t] = nums1[t];
}
for(int t = m, g = 0; t < m + n & g < n; t++,g++) {
temp[t] = nums2[g];
}
int i = 0;
int j = m;
for(int k = 0; k < m + n; k++) {
if(i == m) {
nums1[k] = temp[j];
j++;
} else if(j == m + n) {
nums1[k] = temp[i];
i++;
} else if(temp[i] <= temp[j]) {
nums1[k] = temp[i];
i++;
} else {
nums1[k] = temp[j];
j++;
}
}
}
2.剑指 Offer 51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
这题实际上还是利用数组中的元素两两配对比较,也就是分治处理,不过这次是比较大小后的计数。
public int reversePairs(int[] nums) {
int len = nums.length;
if(len < 2) {
return 0;
}
int[] temp = new int[len];
return mergePairs(nums, 0, len - 1, temp);
}
public int mergePairs(int[] nums, int left, int right, int[] temp) {
//如果是无参void 则可写成 return; 或者不写
if(left >= right) {
return 0;
}
int mid = (left + right) >> 1;
int leftCount = mergePairs(nums, left, mid, temp);
int rightCount = mergePairs(nums, mid + 1, right, temp);
int reverseCount = merge(nums, left, mid, right, temp);
//最后记得返回三者之和
return leftCount + rightCount + reverseCount;
}
public int merge(int[] nums, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
int count = 0;
for(int t = left; t <= right; t++) {
temp[t] = nums[t];
}
for(int k = left; k <= right; k++) {
if(i == mid + 1) {
nums[k] = temp[j++];
} else if(j == right + 1 || temp[i] <= temp[j]) {
nums[k] = temp[i++];
} else {
nums[k] = temp[j++];
count += mid - i + 1;
}
}
return count;
}
这里要说一下逆序数的求法:前提是两个序列有序
如果有两个有序序列:
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:
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
这题和第二题类似,但是这里要解决定位的问题,因为我们的元素节点在归并排序的时候是会移动的,所以需要设置一个索引数组来给这些元素定位。但是求逆序数用的是第二种方法:在前有序数组出列时,计算后有序数组中已经出列的元素个数。

public List<Integer> countSmaller(int[] nums) {
int len = nums.length;
List<Integer> res = new ArrayList<>();
if(len < 2) {
res.add(0);
return res;
}
int[] temp = new int[len];
int[] indexes = new int[len];
int[] result = new int[len];
for(int i = 0; i < len; i++) {
indexes[i] = i;
}
merge(nums, 0, len - 1, temp, indexes, result);
for(int i = 0; i < len; i++) {
res.add(result[i]);
}
return res;
}
public void merge(int[] nums, int left, int right, int[] temp, int[] indexes, int[] result) {
if(left >= right) {
return;
}
int mid = (left + right) >> 1;
merge(nums,left, mid, temp,indexes,result);
merge(nums,mid+1,right,temp,indexes,result);
mergeSort(nums,left,right,mid,temp,indexes,result);
}
public void mergeSort(int[] nums, int left, int right, int mid, int[] temp, int[] indexes, int[] result) {
int i = left;
int j = mid + 1;
for(int t = left; t <= right; t++) {
temp[t] = indexes[t];
}
for(int k = left; k <= right; k++) {
if(i == mid + 1) {
indexes[k] = temp[j++];
} else if(j == right + 1) {
indexes[k] = temp[i++];
result[indexes[k]] += right - mid;
} else if(nums[temp[i]] <= nums[temp[j]]) {
indexes[k] = temp[i++];
result[indexes[k]] += j - mid - 1;
} else {
indexes[k] = temp[j++];
}
}
}
归并排序的思想很重要,在解决负责问题的分治思想有利于将大问题分解。从而更快的解决问题。
参考资料
https://www.cnblogs.com/chengxiao/p/6194356.html
浅谈归并排序:合并 K 个升序链表的归并解法的更多相关文章
- [LeetCode题解]23. 合并K个升序链表 | 分治 + 递归
方法一:分治 + 递归 解题思路 在21. 合并两个有序链表,我们知道如何合并两个有序链表.而本题是合并 k 个有序链表,可以通过大问题拆分成小问题解决,即把 k 个链表,拆分成 k/2 个链表组,俩 ...
- 【LeetCode】23. Merge k Sorted Lists 合并K个升序链表
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:合并,链表,单链表,题解,leetcode, 力扣,Py ...
- LeetCode-023-合并K个升序链表
合并K个升序链表 题目描述:给你一个链表数组,每个链表都已经按升序排列. 请你将所有链表合并到一个升序链表中,返回合并后的链表. 示例说明请见LeetCode官网. 来源:力扣(LeetCode) 链 ...
- leetcode python 012 hard 合并k个有序链表
#[LeetCode] Merge k Sorted Lists 合并k个有序链表(升序) import numpy as npimport time class Node(object): d ...
- 23.合并k个有序链表
合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂度. 示例: 输入: [ 1->4->5, 1->3->4, 2->6 ] 输出: 1-&g ...
- [LeetCode] Merge k Sorted Lists 合并k个有序链表
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 这 ...
- [LeetCode] 23. 合并K个排序链表
题目链接: https://leetcode-cn.com/problems/merge-k-sorted-lists/ 题目描述: 合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂 ...
- [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 ...
- 合并K个排序链表
合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂度. 示例: 输入: [ 1->4->5, 1->3->4, 2->6 ] 输出: 1-&g ...
随机推荐
- Android官方文档翻译 十三 3.1Supporting Different Languages
Supporting Different Languages 支持不同语言 This class teaches you to 这节课教给你 Create Locale Directories and ...
- 【reverse】逆向3 寻找地址
[reverse]逆向3 寻找地址 寻址公式一:[立即数] 读取内存的值: mov eax,dword prt ds:[0x13FFC4] 将内存编号为0x13FFC4.0x13FFC5.0x13FF ...
- AOP操作-准备工作
AOP操作(准备) 1,Spring 框架中一般基于 AspectJ 实现AOP操作 (1)什么是 AspectJ *AspectJ 不是 Spring 组成部分,独立AOP框架,一般把 Aspect ...
- 多线程-其他方法-join等
1 package multithread4; 2 3 /* 4 * toString():返回该线程的字符串表现形式,包括线程名称.优先级和线程组 5 * Thread[Thread-0,5,mai ...
- ssh 信任关系无密码登陆,清除公钥,批量脚本
实验机器: 主机a:192.168.2.128 主机b:192.168.2.130 实验目标: 手动建立a到b的信任关系,实现在主机a通过 ssh 192.168.2.130不用输入密码远程登陆b主机 ...
- 前端vue-TinyMCE富文本编辑器表情插件报错解决
最近项目中需要使用文本编辑器,比较了下最终选择了TinyMCE这款富文本编辑器.我安装的是TinyMCE v5但是在使用表情插件的时候,表情一直都出不来,报错信息如下: Uncaught Syntax ...
- python初学笔记之列表推导式
列表推导式(又称列表解析式)提供了一种简明扼要的方法来创建列表.注意:简而言之,就是把普通的多行for循环压缩成一行代码,这种压缩语法适用于列表.字典.集合等可迭代数据结构(iterables).创建 ...
- Lesson8——Pandas reindex重置索引
pandas目录 1 简介 重置索引(reindex)可以更改原 DataFrame 的行标签或列标签,并使更改后的行.列标签与 DataFrame 中的数据逐一匹配.通过重置索引操作,您可以完成对现 ...
- C#运算符重载---逐步地分析与理解
1.什么是运算符重载 定义:(百科定义)就是把已经定义的.有一定功能的操作符进行重新定义,来完成更为细致具体的运算等功能.操作符重载可以将概括性的抽象操作符具体化,便于外部调用而无需知晓内部具体运算过 ...
- JavaCV的摄像头实战之六:保存为mp4文件(有声音)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...