424-分治算法-对数时间求中位数

分治算法-对数时间求中位数

对数时间:O(logn)

中位数:我们用长度除以2就知道在哪里了。
但是题目有要求,在2个有序的数组当中,去求中位数。如果通过归并思想合并后求中位数,但是在合并的过程中,是O(n)的时间复杂度。

中位数的概念

在这里插入图片描述

问题描述

在这里插入图片描述

解题思路

我们先画了2个数组(2个升序的数组):

在这里插入图片描述
在这里插入图片描述

我们现在的目的是:找这2个升序数组的第top k个元素
我们在2个升序数组中找满足下列的条件:

在这里插入图片描述

只要找到符号这个条件的ij的位置,就意味着在界限的左边的值全部小于界限的右边的值
在这里插入图片描述
(因为这2个数组本身就是升序的)
因为arr[0]…arr[i-1]这部分本身就小于arr[i]…arr[n]
brr[0]…brr[j-1]这部分本身就小于brr[j]…brr[m]
所以,加上刚才列出来的条件:arr[i-1]<brr[j]&&brr[j-1]<arr[i]
所以得出下面这个结论:
在这里插入图片描述
如果我们找到了这样的i和j的位置,我们再来看:

把界限左边的两段的元素个数相加,假如说个数是k,意味着第k个元素的值就是
在这里插入图片描述
这2段序列一合并,这2个元素中的较大的那个元素就是在第k个!!!
在这里插入图片描述

如果说,界限左边的合并的两段算出来的元素个数比k大,就说明我们不需要这么多元素,我们的i,j就可以往前推,即把多余的这些元素往界限后的区间段放。
把界限左边的末尾的元素依次往界限右边的区间段放,也就是把i和j下标往前推,但是依然是满足下面这个条件:
在这里插入图片描述
如果界限左边的合并的两段算出来的元素个数比k小,就把右边的元素往左拿,也是满足上面这个条件的,是不影响的!!!

所以,我们得出下面结论:
在这里插入图片描述

在这里插入图片描述
所以:
在这里插入图片描述

如何找到这个分界的i和j的值?

O(logn)的时间就是花费在这里:找i和j的值
在这里插入图片描述
看到O(logn),我们就要联系到二分搜索树:
我们看下面图解和代码:
begin是第一个数组的起始位置,end是第一个数组的末尾位置
中间元素的下标我们定义为i=(begin+end)/2,二分之后,i的位置就是下图中的虚箭头指向的位置。
因为我们要找第k个,所以既然在第一个数组里选择了i个,那么就要选择第二个数组的k-i个,j=k-i。i+j=k+k-k=k。总共就是k个
在这里插入图片描述

第k个元素就是
在这里插入图片描述
我们处理完,进行判断:
如果说:
在这里插入图片描述
就说明第k个已经找到了,已经找到i和j的位置了!

如果说:
在这里插入图片描述
我们就要缩小二分搜索的范围,我们的end就要进行调整,end=i-1
在这里插入图片描述

如果说
在这里插入图片描述
我们就要从后面要找更大的,就是把begin往前推
在这里插入图片描述

我们用left来表示第k个元素:
然后分2个情况:总长度是奇数,总长度是偶数,这2种情况:
在这里插入图片描述
这一块操作是常量操作哦,比较两个数的大小,求一个和除以2或者直接返回left,这是常量操作。
最终,算法的时间复杂度就是O(logn)

对数时间求中位数代码实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

//O(logn)对数时间求解中位数
double middleValue(vector<int>& nums1, int length1, vector<int>& nums2, int length2)
{
	if (length1 > length2)
	{   //在短的数组中求解合适的i和j值,更快一点
		return middleValue(nums2, length2, nums1, length1);
	}

	if (length1 == 0)
	{
		//0 0 0 0 0 0 0    (6-1)/2=3   
		int k = (length2 - 1) / 2;
		if (length2 % 2 == 0)
		{
			return (nums2[k] + nums2[k + 1]) * 1.0 / 2;
		}
		else
		{
			return nums2[k];
		}
	}

	int i = 0;
	int j = 0;
	int begin = 0;
	int end = length1;
	int k = (length1 + length2 + 1) / 2; 
	while (begin <= end)
	{   //二分搜索的算法思想,对数时间找到i+j = k
		i = (begin + end) / 2;//在numbers1数组里面i的位置
		j = k - i;//因为要满足i+j=k
		if (i > 0 && j < length2 && nums1[i - 1] > nums2[j])
		{
			end = i - 1;//缩小二分搜索的范围,i前移,往前找
		}
		else if (j > 0 && i < length1 && nums2[j - 1] > nums1[i])
		{
			begin = i + 1;//i后退,往后找
		}
		else
		{
			break;//满足arr[i-1] < brr[j] && brr[j-1] < arr[i],找到了
		}
	}

	//我们有注意特别情况:nums1特别短,而且nums1数组的元素的值都特别大
	//我们要找arr[i-1] < brr[j]
	//nums1特别短,而且nums1数组的元素的值都特别大
	//所以也就是i得一直前移找合适的i值,找不到,一直到i==0了
	int left = 0;
	if (i == 0)
	{   //中位数肯定都在num2这个数组当中
		left = nums2[j - 1];
	}
	else if (j == 0)
	{   //nums2这个数组太短了  中位数肯定都在num1这个数组当中
		left = nums1[i - 1];
	}
	else
	{
		left = std::max(nums1[i - 1], nums2[j - 1]);
	}
	int right = 0;
	if (i == length1)
	{ //nums1数组元素太少,而且值都特别的小  中位数肯定都在num2这个数组当中
	//我们要找:brr[j-1] < arr[i],但是nums1数组元素太少,而且值都特别的小
	//根本找不到brr[j-1] < arr[i],就要往i的右边一直缩小范围,i==lenth1代表找不到
		right = nums2[j];
	}
	else if (j == length2)
	{   //中位数肯定都在num1这个数组当中
	   //nums2数组元素太少,而且值都特别的小
		right = nums1[i];
	}
	else//正常情况
	{
		right = std::min(nums1[i], nums2[j]);
	}

	//找到了合适的i和j的值
	if ((length1 + length2) % 2 == 0)
	{   //偶数长度
		return (left + right) * 1.0 / 2;//考虑结果可能是带有小数
	}
	else
	{   //奇数长度
		return left;
	}
}
int main()
{
	vector<int> vec1;
	vector<int> vec2;
	//for (int i = 0; i < 10; ++i) {
	//	vec1.push_back(rand() % 100);
	//}

	for (int i = 0; i < 5; ++i)
	{
		vec2.push_back(rand() % 100);
	}

	vector<int> vec = vec1;
	for (int v : vec2)
	{
		vec.push_back(v);
	}
	sort(vec.begin(), vec.end());
	for (int v : vec)
	{
		cout << v << " ";
	}
	cout << endl;

	sort(vec1.begin(), vec1.end());
	sort(vec2.begin(), vec2.end());

	double midval = middleValue(vec1, vec1.size(), vec2, vec2.size());
	cout << "midval:" << midval << endl;

	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值