问题很简单,就是在两个有序的整数数组里(数组A长度为m, 数组B长度为n),找到两个数组合并后的中位数。
要求时间复杂度 O(log(m+n))
也就是说不能用先归并两个数组再直接查找的办法。
中位数就是在一个有序数组中,位于中间的数字,假如数组元素个数为偶数,则取两个中间数字的平均数。
例:
1,2,3,4,5 中位数为:3
1,2,3,4 的中位数为:2.5
这个问题其实看起来挺简单的,网上的一些博客里说的,很多都是一个大致思想,细节有误,代码也有误。真是误人子弟。
假如数组等长的话,那么很简单就能解决,因为每次比较数组的中间数字,把数字大的数组扔掉右半边,把数字小的数组扔掉左半边,每次循环,到最后就能知道中位数是多少了。
你以为这么简单就OK了? 当然不是!在数组等长的时候需要注意以下几个问题。
3,4,5,6与1,2,7,8
的时候,第一次比较是4
和2
发现4
比较大,因此把5,6
扔掉,另外一个数字扔掉1,2
,但是我们可以发现,这个两个数组的中位数是4,5
两个数字的平均数4.5
,但是在刚才比较的过程中已经把5丢掉了。所以这个时候要注意。A[median1] == B[median2]
的时候停止。(因为1,2,3,4和1,2,4,5)在第一次比较的时候,median1 = 1, median2 = 1
, 那么A[1] = 2, B[1] = 2
虽然两个元素相等,但是这2并不是中位数,中位数是2.5
。但是假如在比较发现相等的时候,数组比较的开始和结束是奇数个元素的话,是可以直接认为相等的。(例如: 1,2,3,4,5和1,2,3,5,7)这个两个数组的中位数就是3。针对注意事项1:我们只用记得返回元素的时候一定是return (double)(a+b)/2
就可以了。
针对注意事项2:在判断两个中位数大小之后,请判断当前(因为是递归,所以每次是有开始结束位置的)是不是两个偶数个元素的中位数比较,如果是则在 A[median1] > B[median2]的时候,割掉(end1 - median1 - 1)个元素。同意数组B割掉也是这么多个元素。
针对注意事项3:A: 1,2,3,4与B: 1,2,5,7
在遇到A[median1] == B[median2]
情况,虽然没有直接停止,但是也是已经找到中位数了,同样判断当前比较的范围是不是都是奇数个元素,如果是:返回这个数字!它就是中位数,如果不是,则返回这个数字2
与其后面数字A数组2后面是3和B数组2的后面5
相对较小数字3
的平均数(2 + min(3,5))/2
。
至此已经解决了等长度的数组求中位数!
其实数组不等长与数组等长的解决办法基本一样!
但是还是要注意每次递归切割的长度问题。
首先在我们切割的时候,先判断数组A和B的长短情况,哪个短,哪个是A,因为切割的时候如果以长数组为准,有可能切掉一半后,短数组早就越界访问了。
注意:图里是
A[median1] < B[median2]
情况,假如是A[median1] > B[median2]
的情况,便如数组等长情况一样,需要判断是不是双方都是偶数个元素,如果是则切割的袁术个数不是end1 - median1
而是end1 - median1 - 1
个数字。
然后我们已经知道如何解决整个问题了。
在数组A,B元素个数和不同情况,结束条件不同。
数组A和数组B的元素个数和为奇数个:
A[median1] == B[median2]
一定结束。A[median1] < B[median2]
返回B[median2]
。假如A[median1] > B[median2] && A[median1] < B[median2+1]
返回A[median1]
。最后假如A[median1] > B[median2+1]
返回B[median2+1]
。以上两个是结束条件,那么有一种情况是死循环条件。就是当循环到一定程度,即数组A只剩下两个元素的时候median1 == start1
,1,2
且比B[median2]
小的时候,已经没有任何切割的了。就会死循环。(其实因为A[median1] > B[median2]
的时候会少切一个元素,所以不论比B[median2]
大或小都需要手动切割)。
这个时候手动切割掉A中不需要的元素就好了, A[median1]
小就切割掉A[median1]
反之切割掉A[end1]
。
然后继续递归就好了,在上面已经说明了遇到A只有一个元素情况的结束条件。
注意!!!! 以上是建立在数组A和数组B总元素数量和为奇数情况。
偶数个数在结束条件上很麻烦!!!!
大家自己写一个例子:
[1,2];[3,4];
---
[1,4];[2,3];
---
[1,3];[2,4,6,7];
---
[5,6];[1,2,4,7];
以上都需要一个个分析,分析A[start1]的位置和A[end1]的位置
。
想想什么样子的位置情况,应该是哪两个数字的平均数就可以了。不难,但是乱!
以下代码在leetcode OJ
上已经AC.
//
// main.cpp
// MedianOfTwoSortArray
//
// Created by Alps on 15/11/2.
// Copyright (c) 2015年 chen. All rights reserved.
//
#include
#include
using namespace std;
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int length1 = (int)nums1.size();
int length2 = (int)nums2.size();
//如果有一个数组为空
if (length1 == 0) {
if (length2 %2 == 0) {
return (double)(nums2[length2/2]+nums2[length2/2 - 1])/2;
}else{
return (double)nums2[length2/2];
}
}
//如果有一个数组为空
if (length2 == 0) {
if (length1 %2 == 0) {
return (double)(nums1[length1/2]+nums1[length1/2 - 1])/2;
}else{
return (double)nums1[length1/2];
}
}
//如果把数组长度短的放在前面
if (length1 <= length2) {
return findMedian(nums1, 0, length1-1, nums2, 0, length2-1);
}else{
return findMedian(nums2, 0, length2-1, nums1, 0, length1-1);
}
}
//返回相对小的数字
int minNumber(int a, int b){
return a < b ? a : b;
}
//查找主函数
double findMedian(vector<int>& nums1, int start1, int end1, vector<int>& nums2, int start2, int end2){
// 如果两个数组都只有一个数字(为空的已经在之前判断过了)
if (start1 == end1 && start2 == end2) {
return ((double)nums1[0]+(double)nums2[0])/2;
}
int offset = 0;//每次切割的偏移量
int median1 = (start1 + end1)/2;//求中间数字下标
int median2 = (start2 + end2)/2;//求中间数字下标
//数组A只有一个数字,而数组B不止一个数字的情况
if (start1 == end1 && start2 != end2) {
if ((end2 - start2) % 2 == 0) { //判断数组B如果是偶数个数字
if (nums1[start1] >= nums2[median2-1] && nums1[start1] <= nums2[median2+1]) {
return (double)(nums1[start1]+nums2[median2])/2;
}else if(nums1[start1] < nums2[median2-1]){
return (double)(nums2[median2-1] + nums2[median2])/2;
}else{
return (double)(nums2[median2] + nums2[median2+1])/2;
}
}else{//如果奇数个数字
if (nums1[start1] >= nums2[median2] && nums1[start1] <= nums2[median2+1]) {
return (double)nums1[start1];
}else if(nums1[start1] < nums2[median2]){
return (double)nums2[median2];
}else{
return (double)nums2[median2+1];
}
}
}
// 如果中间下标数字相等
if (nums1[median1] == nums2[median2]) {
if ((nums1.size() + nums2.size())%2 != 0 || end1-start1 % 2 == 0) {//总元素是奇数个或者两个数组范围内都是奇数个元素
return nums1[median1];
}else{//否则
return (double)(nums1[median1]+minNumber(nums1[median1+1] , nums2[median2+1]))/2;
}
}else if (nums1[median1] < nums2[median2]){
if (median1 == start1) {//假如A范围到两个数字的情况了
if ((nums1.size() + nums2.size())%2 == 0) {//麻烦的判断
if (nums1[start1+1] >= nums2[median2+1]) {
return (double)(nums2[median2]+nums2[median2+1])/2;
}else if(median2 != 0 && nums1[start1+1] <= nums2[median2-1]){
return (double)(nums2[median2]+nums2[median2-1])/2;
}else{
return (double)(nums2[median2]+nums1[start1+1])/2;
}
}else{
return findMedian(nums1, start1 + 1, end1, nums2, start2, end2-1);
}
}
offset = median1-start1;//正常的offset
return findMedian(nums1, start1+offset, end1, nums2, start2, end2-offset);
}else{
if (median1 == start1) {
if ((nums1.size() + nums2.size())%2 == 0) {
if (nums1[end1] <= nums2[median2+1]) {
return (double)(nums1[median1] + nums1[start1+1])/2;
}else if ((nums1[end1] > nums2[median2+1] && nums1[start1] <= nums2[median2+1]) || (median2+2 < nums2.size() && nums1[median1] <= nums2[median2+2]) || (median2+2 >= nums2.size() && nums1[start1] >= nums2[median2+1])){
return (double)(nums1[median1]+nums2[median2+1])/2;
}else {
return (double)(nums2[median2+1]+nums2[median2+2])/2;
}
}else{
return findMedian(nums1, start1, end1-1, nums2, start2+1, end2);
}
}
offset = end1-median1;
if ((end1-start1)%2 != 0 && (end2-start2)%2 != 0) {
offset -= 1; //需要少切割一个的offset
}
return findMedian(nums1, start1, end1-offset, nums2, start2+offset, end2);
}
return 0;
}
};
int main(int argc, const char * argv[]) {
Solution sl;
int num1[] = {1,5, 6, 7};
int num2[] = {2,3,4,8,9,10};
vector<int> nums1(num1, num1+4);
vector<int> nums2(num2, num2+6);
printf("%f\n",sl.findMedianSortedArrays(nums1, nums2));
return 0;
}
以上就是这个问题的解决代码了。我的代码量是稍微有点大的。所以可能不是很好看。后面我看看有没有更简单的代码解决办法。