本文已参与「新人创作礼」活动,一起开启掘金创作之路。
题目链接: leetcode.com/problems/me…
1. 题目介绍(合并两数组求中值)
Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays.
【Translate】: 给定两个大小分别为m和n的有序数组nums1和nums2,返回这两个有序数组的中值。(这里说的有些不够明显,如果不看测试用例,很容易让人理解失误。这里的真实题意是返回合并两个数组后,并完成排序后的中值)
The overall run time complexity should be O(log (m+n)).
【Translate】: 要求总体运行时复杂度应为O(log (m+n))。(题目不算难,但它之所以被定级为Hard问题,难度就在于此)
测试用例:
约束:
2. 题解
2.1 冒泡排序 -- O(n^2)
这个题的题意一点也不复杂,摆开了揉碎了其实就是将两个数组合并后排序取中值。
- 奇数:
arr.length/2
- 偶数:
(arr.length/2-1 + arr.length/2)/ 2
但是普通的排序算法中最快的时间复杂度也只是O(N*logN),达不到题目所要求的O(log(m+n))。所以,这道题其实比拼的就是能否寻找到一种方法使得不通过排序来实现求取中值的操作。
下方的题解是我通过正常思路,使用冒泡排序实现的。它就是通过System.arraycopy()
将nums1和nums2两个数组合并成了一个数组arr,然后再通过冒泡排序对其进行排序,最后判断奇偶,求取中值。当然这种方法没有达到题目要求,显然效率不高。
class Solution {
// 冒泡排序
public static int[] bubbleSort(int[] a) {
int len = a.length;
// 外层控制比较轮次
for (int i = 0; i < len; i++) {
// 内层控制比较次数
for (int j = 0; j < len - i - 1; j++) {
if (a[j] > a[j+1]){
int tmp = a[j];
a[j] = a[j+1];
a[j+1] =tmp;
}
}
}
return a;
}
public double findMedianSortedArrays1(int[] nums1, int[] nums2) {
int[] arr = new int[nums1.length + nums2.length];
System.arraycopy(nums1,0,arr,0,nums1.length);
System.arraycopy(nums2,0,arr,nums1.length,nums2.length);
arr = bubbleSort(arr);
System.out.println(Arrays.toString(arr));
System.out.println(arr.length);
if(arr.length % 2 == 0){
return (double)(arr[arr.length/2-1]+arr[arr.length/2])/2;
}else{
return (double)arr[arr.length/2];
}
// return 0;
}
}
2.2 if...限定大法 -- O(m+n)
这是 Pioneer4 在Discuss中提供的方法,目前在Java Tags中排名第一,其时间复杂度是O(m+n),空间复杂度是O(1)。
该题解是通过各种条件的if语句来实现中点的定位和变相的排序。想要频繁使用if语句定义限制条件,这就要求要有良好的逻辑思维能力,能够理清这里面的各种关系,这也是作者值得让人钦佩的地方。
在研究这道题解的时候,突然醒悟了一个关键点,就是这两个给定的数组都是有序的,如果是无序的,那么这个题解就不适用了。其原因在于,for循环中限定了执行次数是两个数组长度之和的一半,这是为了能够正确的确定中点的位置。其次,该题解是将index1和index2着两个变量作为两个数组的下标才进行移动,由于两个数组都是有序的,那么我们就可以通过比较两个数组的大小,进行变相的排序,从而逐步移动到中点。为了防止一个数组元素过多,一个数组元素过少的情况,设置限定条件,即当一个数组已经遍历完毕,就一直移动另一数组,直至循环结束。
public double findMedianSortedArrays2(int[] nums1, int[] nums2) {
int index1 = 0;
int index2 = 0;
int med1 = 0;
int med2 = 0;
for (int i=0; i<=(nums1.length+nums2.length)/2; i++) {
med1 = med2;
if (index1 == nums1.length) {
med2 = nums2[index2];
index2++;
} else if (index2 == nums2.length) {
med2 = nums1[index1];
index1++;
} else if (nums1[index1] < nums2[index2] ) {
med2 = nums1[index1];
index1++;
} else {
med2 = nums2[index2];
index2++;
}
}
// the median is the average of two numbers
if ((nums1.length+nums2.length)%2 == 0) {
return (float)(med1+med2)/2;
}
return med2;
}
2.3 二分搜索:两个大小不同的排序数组的中位数 -- O(log(m+n))
Tushar Roy的代码,有兴趣的朋友可以点击链接去看看他的视频讲解。
该题解使用的是Binary Search的思想,二分查找有两个要求,一个是数列有序,另一个是数列使用顺序存储结构。嘿!这道题天生就该用二分查找。二分查找的核心思想就是将查找的值和数组的中间值作比较
,如果被查找的值小于中间值,就在中间值左侧数组继续查找;如果大于中间值,就在中间值右侧数组中查找;否则中间值就是要找的元素。除此之外,二分并不代表一定是二,也可以是三,可以是N,只是一种表述,表达的意思是以最快的速率将搜索数据的范围缩小。
在该题解中,就是这样。通过partX和partY两个变量分别确定nums1和nums2的中间值下标。xLeft、xRight、yLeft、yRight则用来保存中间值。如果出现xLeft<=yRight && yLeft<=xRight
则说明可以找出中间值,而如果是xLeft>yRight,则说明nums1数组中取到的中值偏大;如果都不是,则说明nums2数组中取到的中值偏大;我们应该让high-1或low+1逐步去逼近那个中值,达到xLeft<=yRight && yLeft<=xRight
的状态。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if(nums1.length > nums2.length)return findMedianSortedArrays(nums2,nums1);
int x = nums1.length;
int y = nums2.length;
int low = 0;
int high = x;
while(low<=high){
int partX = (low+high)/2;
int partY = (x+y+1)/2 - partX;
int xLeft = partX == 0 ? Integer.MIN_VALUE : nums1[partX-1];
int xRight = partX == x ? Integer.MAX_VALUE : nums1[partX];
int yLeft = partY == 0 ? Integer.MIN_VALUE : nums2[partY-1];
int yRight = partY == y ? Integer.MAX_VALUE : nums2[partY];
if(xLeft<=yRight && yLeft<=xRight){
if((x+y)%2==0){
return ((double)Math.max(xLeft,yLeft) + Math.min(xRight,yRight))/2;
}else{
return Math.max(xLeft,yLeft);
}
}else if(xLeft>yRight){
high = partX -1;
}else{
low = partX+1;
}
}
return 0;
}
}
3. 可参考
[1] 【java算法】 两个数组合并成一个数组,并进行排序,打印出来
[6] 二分查找思想与实现