4. Median of Two Sorted Arrays

博客围绕两个有序数组 nums1 和 nums2 展开,目标是找出它们的中位数,要求时间复杂度为 O(log (m+n))。介绍了有序数组用二分查找法,两个有序数组找第 K 大数的删除法,无序数组找第 K 大数的快速排序划分法和堆排序法,还给出本题的两种解法。

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

You may assume nums1 and nums2 cannot be both empty.

Example 1:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0

Example 2:

nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

1、记住,有序数组一定用二分查找法,两个有序数组就控制变量,二分查找短的,另一个根据要求二分查找

2、两个有序数组找第K大的数就不断删除前面p个小于K的数,对于删除之后的数组查找第K-p的数,一直删到K==1

3、对于无序数组查找第K大的数两个思路:

  • 第一个快速排序划分法,当划分位置position==K-1则返回A[position-1]
  • 第二个思路是堆排序,不断建立大顶堆,执行n-k+1次

本题提供两个接法:

  • 第一个控制变量二分查找,只二分查找短的数组,长的数组控制变量 C1+C2=k
  • 第二个接法将题目变形成在两个有序数组中查找第K大的数,通过删去前面p个数,查找第K-p的数,不断缩短两个数组的长度。
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		int n = nums1.size(), m = nums2.size();
		if (n > m)
			return findMedianSortedArrays(nums2, nums1);
		int total = m + n;
		if (total & 0x1)
			return findKth(nums1, nums2, total / 2+1);
		else
			return (findKth(nums1, nums2, (total) / 2)+findKth(nums1, nums2, total / 2+1))/2.0;
	}

public:
	int findKth2(vector<int> &nums1, vector<int> &nums2, int k)
	{
		//方法一,二分查找,固定一个数组二分,另一个根据等式c1+c2=k确定数组二切分点
		//注意边界,这里保证n<m恒成立,二分小的数组,防止大的越界
		//保证两个数组和为奇数  可以虚拟加入#
		//通过index的映射虚拟加入,(index-1)/2
		//#号为切割点,左边为(index-1)/2  右边为index/2
		//理想状态下L1<R2	L2<R1 并且此时C1+C2=k  那么第k个数是min(L1,L2)
		//如果L1>R2 说明L1应该往左移变小	C1左边二分
		//如果L2>R1 说明R1应该右移变大		C1右边二分
		//为了满足最后的判断条件
		//C1==0时,说明数组1的值太大了在2中搜索就好了,但是要将L1置为一个小数
		//C1==2*n时,说明数组1的值太小了,在2中搜索就好了,但是要将R1置为大数
		//C2==0 将L2置为小数
		//C2==2*m,将R2置为大数
		//最后二分结束,出现L1==R2 L2==R1,max(L1,L2)即为第k个数
		int n = nums1.size(), m = nums2.size();
		if (n > m)
			//保证第一个为短的
			return findKth2(nums2, nums1, k);
		if (n == 0)
			return nums2[k - 1];
		if (m == 0)
			return nums1[k - 1];
		if (k > m + n)
			return -1;
		int L1, L2, R1, R2, C1, C2;
		int low = 0, high = 2 * n;
		while (low <= high)
		{
			//首先确定数组切割位置
			C1 = (low + high) / 2;
			//假装认为原来为n+m,现在为2n+2m+2
			C2 = 2*k - C1;		//数组扩充  所以第k个数变成第2*k个数
			C2 = (C2 <= 0 ? 0 : C2);
			C2 = (C2 >= m * 2 ? m * 2 : C2);		//当C2切割的时候越界,需要限制一下大小,这时候说明数很靠后
			//给切分数组之后的左右部分赋值
			L1 = (C1 <= 0 ? INT_MIN : nums1[(C1 - 1) / 2]);
			R1 = (C1 >= 2 * n ? INT_MAX : nums1[C1 / 2]);
			L2 = (C2 <= 0 ? INT_MIN : nums2[(C2 - 1) / 2]);
			R2 = (C2 >= 2 * m ? INT_MAX : nums2[C2 / 2]);		//分割边界 即#2#4#6# 当C2=6时,L=6 R=999
			//开始更改切割位置
			if (L1 > R2)
				//L1大了,应该往左边找
				high = C1 - 1;
			else if (L2 > R1)
				//R1小了往右边找
				low = C1 + 1;
			else
				//此时L1==R2 L2==R1
				//此时第k个数找到了 max(L1,L2)
				break;
		}
		return L1>L2? L1:L2;
	}
	int findKth(vector<int> &nums1, vector<int> &nums2, int k)
	{
		//递归求解,如果求数组第k个数,前面删去x个,那么就是求删数之后第k-x个的数
		//仍然采用数组切分的方法,但是不是二分切割
		//在A数组中中切第k/2的数,在B数组中也也切分第k/2的数
		//1、如果满足A[k/2-1]<B[k/2] && B[k/2-1]<A[k/2]
		//或者A[k/2-1]==B[k/2-1](一定存在A[k/2-1]<B[k/2] && B[k/2-1]<A[k/2])
		//则max(A[k/2-1], B[k/2-1])即为第k的数
		//2、但是如果存在A[k/2-1]<B[k/2-1]
		//说明A[0]...A[k/2-1]这k/2个数,一定要会小于第k个数
		//因为A[k/2-1](有k/2个数), B[k/2-1](有k/2个数),
		//这两个最大值可能是第k个数,那么小的那个最多可能是第k-1的数
		//A[k/2-1]<B[k/2-1]那么肯定的,A[k/2-1]<=M[k-2](M为归并排序结果)
		//所以,我们可以将这k/2个数删掉,删掉前k/2个数,那么就相当于找第k-k/2的数
		//3、当A[k/2-1]>B[k/2-1]
		//显然B[k/2-1]<=M[k-2]恒成立,删掉B中前k/2个数即可
		
		
		//所以可以递归进行递归删除前面较小的数
		//一直删除较少的数,那么A的长度、B的长度、k值就会一直变小
		//那么当A.size()==0 A删完了,那么第k个数一定在B中 为 B[k-1]
		//同理B.size()==0 B删完了,那么第k个数一定在A中, 为 A[k-1]
		//如果k比较垃圾,k删完了 那么就是找第0个数
		//min(A[0], B[0])


		//之前的分析都是构建在A(n)、B(m)数组个数都是大于k/2的假设上面建立的
		//如果说某一个数组,A[k/2-1] 或者B[k/2-1]越界了
		//那么就不能用k/2划分数组了,就要用控制变量法  
		//一定要满足划分C1+C2=k才能保障找到的
		//A[C1]与B[C2]进行对比是和M[k-2]与M[k-1]相关的对比
		//如果大的越界 即大的数组元素个数都 < k/2 那就没得玩了,直接返回-1报错拉到
		//此时显然n+m<k  
		//如果小的越界了,还能玩一玩  假设A小,n<m&&n<k/2
		//那我们就查看A中最大的数A[A.size()]是否满足第k个数的条件  
		//如果不满足直接将A全部删除,第k个数变成k-A.size()  在B中  B[k-A.size()-1]
		//所以这时候划分为C1=n,  C2=k-n

		int n = nums1.size(), m = nums2.size();
		if (n + m < k || k < 1)
			//俩个都太短了,垃圾
			return -1;
		if (n > m)
			//保证第一个为短的
			return findKth(nums2, nums1, k);	
		if (n == 0)
			//如果一个被删完了,那么第k个数在领个数组里
			return nums2[k - 1];
		if (m == 0)
			return nums1[k - 1];
		if (k == 1)
			//k比较垃圾被删完了
			//返回俩个数组第一个较小的数
			return nums1[0] < nums2[0] ? nums1[0] : nums2[0];

		//是时候表演真正的技术了 开始递归
		int pa, pb;	//俩个数组的左半部分
		pa = (k / 2 < n ? k / 2 : n);	//防止越界
		pb = k - pa;	//控制变量
		if (nums1[pa - 1] == nums2[pb - 1])
			//两个一样,则返回较大的 其实一样大,两个都行
			return nums1[pa - 1];
		else if (nums1[pa - 1] < nums2[pb - 1]) 
			//nums1数组前半部分比较小,删去
			return findKth(vector<int> (nums1.begin()+pa, nums1.end()), nums2, k - pa);
		else
			return findKth(nums1, vector<int>(nums2.begin()+pb, nums2.end()), k - pb);
		

	}
};

void main()
{
	/*vector<int> nums1 = { 1,2,3,4,11,22 };
	vector<int> nums2 = { 0,2 };*/
	vector<int> nums1 = { 10000001 };
	vector<int> nums2 = { 10000000 };
	Solution *s = new Solution();
	double k = s->findMedianSortedArrays(nums1, nums2);
	//int k = s->findKth(nums1, nums2, 10);
	printf("%.3lf\n", k);
	delete s;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值