第 k 小的数
一、寻找两个有序数组的中位数
1.1 问题描述
给定两个大小为 m 和 n 的不同时为空的有序数组 nums1 和 nums2。找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
1.2 算法分析
题目要求的时间复杂度是 O(log(m + n)),要产生这样级别的时间复杂度只有采用二分查找法,用分治递归的思路来考虑这个问题。
需要转换题目中求中位数的问题为求第 k 小数的问题。如果 m + n 是奇数,那么寻找第 k = (m + n)/2 + 1 小的数即可;如果长度和是偶数,那么我们还需要寻找第 (m + n)/2 小的数,然后计算两数的平均值。
在求解整个问题的过程中,我们始终需要考虑一个很重要的问题--数组索引越界问题。
下面将详细地分析整个递归流程。
①、首先定义递归函数的作用:寻找两个有序数组 nums1 数组中 [L1, R1] 范围内和 nums2 数组 [L2, R2] 范围内第 k 小的数,k 从 1开始计数。
/**
* L1 nums1数组的寻找范围的左边界
* R1 nums1数组的寻找范围的右边界
* L2 nums2数组的寻找范围的左边界
* R2 nums2数组的寻找范围的右边界
* k 需要寻找第k小的元素
*/
int findKth(int[] nums1, int L1, int R1, int[] nums2, int L2, int R2, int k)
②、用 len1 = R1 - L1 + 1 来记录 nums1 数组中寻找范围的长度,用 len2 = R2 - L2 + 1 来记录 nums2 数组中寻找范围的长度。
③、如果要寻找的 k > len1 + len2,就像只有 3 个数字要找第 4 小的数一样,超出寻找区域,显然无法找到。
④、递归的终止条件:
当 len1 = 0 时,说明只有 nums2 数组中有元素,直接取 nums2[L2 + k - 1] 位元素即可。
当 k = 1 时,说明要取的是两个有序数组中的最小值 MIN(nums1[L1], nums2[L2])。
⑤、递归过程:
由于要求的是第 k 小的数,而且是在两个有序数组中求。划分两个数组时按照 k 值来分。取变量 i = MIN(len1, k/2),之所以这么取,是为了防止 L1 + k/2 - 1 > len1 导致从 nums1 取值越界。再取变量 j = MIN(len2, k/2)。
接下来比较 nums1[L1 + i - 1] 和 nums2[L2 + i - 1] 这两个值。
如果 nums1[L1 + i - 1] <= nums2[L2 + j - 1],显然 nums1 数组中索引为 L1 + i - 1 及之前的元素不可能是中位数,去除 nums1 数组中 [L1, L1 + i - 1] 范围内的元素,缩小了查找范围。我们递归调用该函数,此时在 nums1 中的查找范围变成了 nums1[L1 + i, R1],此时要找的也不应该是第 k 小的元素,因为已经剔除了 i 个比 k 小的元素,因此我们要找的元素变成了第 k - i 小的元素。
如果 nums1[L1 + i - 1] > nums2[L2 + j - 1],同理,nums2 数组中索引为 L2 + j - 1 及之前的元素不可能是中位数,缩小查找范围,剔除了 j 个比 k 小的元素,因此我们要找的元素变成了第 k - j 小的元素。
因为 i + j = MIN(len1, k/2) + MIN(len2, k/2) <= k,所以可以直接判断 [L1, L1 + i - 1] 或者 [L2, L2 + j -1] 区间的元素不可能是中位数。

总结:算法的思想是不断的剔除数据,逐渐逼近第 k 小的数。
1.3 时间复杂度
假设数组长度足够长,每次剔除的元素都是 k/2(i 或者j),显然我们需要 log(k) 次才能找到第 k 小数,这和二分查找法是同理的,而我们要找的 k 值要么是 (m + n)/2 + 1,要么额外再加上 (m + n)/2,因此时间复杂度是 O(log(m + n)) 级别的。
1.4 代码实现
#define MIN(a, b) (a) < (b) ? (a) : (b)
int findKth(int* nums1, int left1, int right1, int* nums2, int left2, int right2, int k)
{
int n1 = right1 - left1 + 1;
int n2 = right2 - left2 + 1;
// 递归退出条件
if(k > n1 + n2) {
return 0; // 实际上 k 不会小于 n1 + n2
}
if(n1 == 0) {
return nums2[left2 + k - 1];
}
else if (n2 == 0) {
return nums1[left1 + k - 1];
}
if(k == 1) {
return MIN(nums1[left1], nums2[left2]);
}
int i = MIN(n1, k / 2);
int j = MIN(n2, k / 2);
// 剔除比第 k 小的数还小的数,逐渐逼近
if(nums1[left1 + i - 1] > nums2[left2 + j - 1]) {
return findKth(nums1, left1, right1, nums2, left2 + j, right2, k - j);
}
else {
return findKth(nums1, left1 + i, right1, nums2, left2, right2, k - i);
}
}
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size)
{
// k = (nums1Size + nums2Size) /2 + 1,因为 k 从 1 开始计数
int mid1 = findKth(nums1, 0, nums1Size - 1, nums2, 0, nums2Size - 1, (nums1Size + nums2Size) / 2 + 1);
// 两个数组总长度是奇数
if((nums1Size + nums2Size) % 2 != 0) {
return mid1;
}
// 两个数组总长度是偶数
else {
// 额外求 (nums1Size + nums2Size) / 2 的值
int mid2 = findKth(nums1, 0, nums1Size - 1, nums2, 0, nums2Size - 1, (nums1Size + nums2Size) / 2);
return (mid1 + mid2) / 2.0;
}
}
二、内容来源
第 k 小的数的更多相关文章
- *HDU2852 树状数组(求第K小的数)
KiKi's K-Number Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)T ...
- 计算序列中第k小的数
作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4046399.html 使用分治算法,首先选择随机选择轴值pivot,并使的序列中比pivot ...
- [LeetCode] Find K-th Smallest Pair Distance 找第K小的数对儿距离
Given an integer array, return the k-th smallest distance among all the pairs. The distance of a pai ...
- #7 找出数组中第k小的数
「HW面试题」 [题目] 给定一个整数数组,如何快速地求出该数组中第k小的数.假如数组为[4,0,1,0,2,3],那么第三小的元素是1 [题目分析] 这道题涉及整数列表排序问题,直接使用sort方法 ...
- 无序数组求第k大/第k小的数
根据http://www.cnblogs.com/zhjp11/archive/2010/02/26/1674227.html 博客中所总结的7种解法,我挑了其中的解法3和解法6进行了实现. 解法3: ...
- 查找第K小的数 BFPRT算法
出处 http://blog.csdn.net/adong76/article/details/10071297 BFPRT算法是解决从n个数中选择第k大或第k小的数这个经典问题的著名算法,但很多人并 ...
- 基于快速排序思想partition查找第K大的数或者第K小的数。
快速排序 下面是之前实现过的快速排序的代码. function quickSort(a,left,right){ if(left==right)return; let key=partition(a, ...
- 算法---数组总结篇2——找丢失的数,找最大最小,前k大,第k小的数
一.如何找出数组中丢失的数 题目描述:给定一个由n-1个整数组成的未排序的数组序列,其原始都是1到n中的不同的整数,请写出一个寻找数组序列中缺失整数的线性时间算法 方法1:累加求和 时间复杂度是O(N ...
- cogs930找第k小的数(k-th number)
cogs930找第k小的数(k-th number) 原题链接 题解 好题... 终极版是bzoj3065(然而并不会) 先讲这个题... 维护\(n+1\)个值域线段树(用主席树),标号\(0\) ...
- 选择问题(选择数组中第K小的数)
由排序问题可以引申出选择问题,选择问题就是选择并返回数组中第k小的数,如果把数组全部排好序,在返回第k小的数,也能正确返回,但是这无疑做了很多无用功,由上篇博客中提到的快速排序,稍稍修改下就可以以较小 ...
随机推荐
- iOS开发日常笔记01
为什么有initWithCoder还要awakeFromNib? awakeFromNib相较于initWithCoder的优势是:当awakeFromNib执行的时候,各种IBOutlet也都连接好 ...
- 使用webpack从0搭建多入口网站脚手架,可复用导航栏/底部通栏/侧边栏,根据页面文件自动更改配置,支持ES6/Less
之前只知道webpack很强大,但是一直没有深入学习过,这次从头看了一下教程,然后从0开始搭建了一个多入口网站的开发脚手架,期间遇到过很多问题,所以有心整理一下,希望能给大家一点帮助. 多HTML网站 ...
- MySQL数据库无完整备份删库,除了跑路还能怎么办?
1.背景 前段时间,由于运维同事的一次误操作,清空了内网核心数据库,导致了公司内部管理系统长时间不可用,大量知识库内容由于没有备份险些丢失. 结合这两天微盟的删库跑路事件,我们可以看到,数据库的备份与 ...
- java 几种锁实现
public class SyncronizedTest { private int value = 1; private AtomicInteger value1 = new AtomicInteg ...
- nes 红白机模拟器 第7篇 编译使用方法
模拟器,基于 InfoNES ,作者添加修改以下功能: 1, joypad 真实手柄驱动程序(字符型设备驱动) 2,原始图像只有256*240 ,添加 图像放大算法,这里实现了2种,a, 最近邻插值 ...
- 百度地图API-创建多个坐标,连线,信息提示
这是一个多坐标创建,并连线,和信息显示的例子 <!DOCTYPE html> <html> <head> <meta http-equiv="Con ...
- 树莓派上搭建唤醒词检测引擎 Snowboy
Snowboy 是一款高度可定制的唤醒词检测引擎,可以用于实时嵌入式系统,并且始终监听(即使离线).当前,它可以运行在 Raspberry Pi.(Ubuntu)Linux 和 Mac OS X 系统 ...
- 解决idea的pom.xml文件不自动导包问题
在用pom添加新项目的包时,突然发现pom文件不能自动导包,找了半天发现用以下两个步骤就能解决: 一:打开setting 二:找到maven
- C++基础 学习笔记之一:源代码的格式化
C++基础 学习笔记之一:源代码的格式化 1. 源代码中的标记与空白 C++中的语句是以分号表示语句的结束.在C++中空格和回车以及制表符均为相同作用,即三者通常可以互相替代. 例如可以将一个简单的m ...
- mysql 如果没有密码 就不能远程连接
mysql 如果没有密码 就不能远程连接