【LeetCode】4.寻找两个正序数组的中位数

4.寻找两个正序数组的中位数

一、问题描述

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

二、问题简化

 所谓中位数,就是指一个数组中间位置的数字。当元素数量为奇数时,就是最中间的那个数字;而当元素数量为偶数时,为中间两个数字的平均值(求和除以2)。

这里用数列表示并不恰当,数列的定义是正整数集。所以仍旧以模板T抽象表示元素类型,不过对于抽象的类型T来说,我们要求中位数就必须使用求和操作除以2的操作,再考虑到整型除以2会发生截断操作,干脆就只返回数字即可:

//返回值类型,0个元素代表原数组均为空,1个元素代表奇数,2个元素代表偶数
vector<T>

这里的问题实际并没有描述清楚,中位数本质是在一个数组中寻找,而题目的意思是合并数组后再寻找。所以也不一定是只合并两个数组,所以传入数组的参数如下:

list<vector<T>*>

所以函数接口为:

template<typename T>
vector<T> findMedian(list<vector<T>*>); 

三、功能实现

最容易想到的办法是按序合并所有数组后,再判断奇偶返回对应的值。

而合并数组不仅会占据所有数组大小的空间,并且还涉及到元素的拷贝,这样就太慢了!这个区别不是数量级的区别,而是做一千个俯卧撑和动一千下手指的区别。

1.求出合并后的数组总长度M(整型),很明显我们并不需要真的合并,总长度就是各个数组长度之和。

2.如果M为奇数,则中位数为M/2位置的数;如果M为偶数,则中位数为M/2 - 1和M/2位置的数的平均值。(整型除法,直接截断)

3.如何遍历到指定位置的数?每个数组记录下一个值,为当前位置。每次遍历从数组当前位置选择最小值的那个,然后选中的那个数组当前位置增加1。(这实际是一个可抽象的问题,从N个有序数组中,返回合并后的数组,指定位置的元素)

所以我先实现了从N个有序数组中寻找指定位置的元素这个功能:

//从N个有序数组中寻找 指定位置的元素
//所有参数均为升序
template<typename T>
vector<T> FindSortedArrays(const vector<vector<T>*>& all_vec, const vector<int>& vec_target, int m = -1)
{
	//没有数组,返回错误
	if (all_vec.empty()
		|| vec_target.empty())
		return vector<T>();

	//总长度
	if (m == -1)
	{
		m = 0;
		for (auto& iter : all_vec)
		{
			m += iter->size();
		}
	}

	//总长度错误
	if (m <= 0)
		return vector<T>();

	int all_vec_size = all_vec.size();
	//每个数组设置一个位置标记
	vector<int> arr_pos(all_vec_size, 0);

	//目标位置 当前
	int i_target = 0;
	vector<T> ret;

	for (int i = 0; i != m; ++i)
	{
		//从所有数组的当前位置返回最小值
		bool b_inited_min = false;
		T v_min;
		int j_min;//最小值所在的j

		for (int j = 0; j != all_vec_size; ++j)
		{
			vector<T>& vec_ref = *all_vec[j];
			int cur_pos = arr_pos[j];
			//越界跳过
			if (cur_pos >= vec_ref.size())
				continue;
			//最小值未初始化
			if (!b_inited_min)
			{
				b_inited_min = true;
				v_min = vec_ref[cur_pos];
				j_min = j;
			}
			else
			{//已有最小值,则比较覆盖
				if (vec_ref[cur_pos] < v_min)
				{
					v_min = vec_ref[cur_pos];
					j_min = j;
				}
			}
		}

		//断言:未找到最小值
		if (!b_inited_min)
		{
			return vector<T>();
		}

		//最小数组当前位置加1
		++arr_pos[j_min];

		if (i == vec_target[i_target])
		{
			ret.push_back(v_min);
			if (++i_target >= vec_target.size())
				return ret;
		}
	}

	return ret;
}

测试结果如下:

我一共输入了4个数组,并且通过FindSortedArrays函数直接返回了合并之后的指定位置值(函数内部没有实际的合并操作,这个函数叫GetSortedArrays应该更合适)。

然后寻找中位数就变得很简单,通过&1判断奇偶即可,奇数返回m/2位置的值,偶数返回m/2 - 1 ,m/2的平均值:

//寻找中位数
template<typename T>
vector<T> FindMedian(const vector<vector<T>*>& all_vec)
{
	//没有数组,返回错误
	if (all_vec.empty())
		return vector<T>();

	//计算总长度
	int m = 0;
	for (auto& iter : all_vec)
	{
		m += iter->size();
	}

	//奇数
	if (m & 1)
	{
		return FindSortedArrays(all_vec, { m / 2 }, m);
	}
	else
	{
		return FindSortedArrays(all_vec, { m / 2 - 1, m / 2 }, m);
	}
}

四、测试结果

我再复制几组数据用于测试:

 

网上代码的一般思路是实际合并了数组再直接返回对应位置的值,而这个合并操作就占据了大量的空间和时间,而我的代码的优势在于不必占据大量的临时空间,还可以减少合并时的拷贝操作。但是仍然需要比较很多次和拷贝一定次数。

等有空了我再补充测试一下它们之间到底谁更快。不过我在LeetCode里面有测试,经过我反复尝试很明显编译器没有做优化,而且输入数据量也很低,它的速度没什么参考价值。

完整代码如下:

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

//从N个有序数组中寻找 指定位置的元素
//所有参数均为升序
template<typename T>
vector<T> FindSortedArrays(const vector<vector<T>*>& all_vec, const vector<int>& vec_target, int m = -1)
{
	//没有数组,返回错误
	if (all_vec.empty()
		|| vec_target.empty())
		return vector<T>();

	//总长度
	if (m == -1)
	{
		m = 0;
		for (auto& iter : all_vec)
		{
			m += iter->size();
		}
	}

	//总长度错误
	if (m <= 0)
		return vector<T>();

	int all_vec_size = all_vec.size();
	//每个数组设置一个位置标记
	vector<int> arr_pos(all_vec_size, 0);

	//目标位置 当前
	int i_target = 0;
	vector<T> ret;

	for (int i = 0; i != m; ++i)
	{
		//从所有数组的当前位置返回最小值
		bool b_inited_min = false;
		T v_min;
		int j_min;//最小值所在的j

		for (int j = 0; j != all_vec_size; ++j)
		{
			vector<T>& vec_ref = *all_vec[j];
			int cur_pos = arr_pos[j];
			//越界跳过
			if (cur_pos >= vec_ref.size())
				continue;
			//最小值未初始化
			if (!b_inited_min)
			{
				b_inited_min = true;
				v_min = vec_ref[cur_pos];
				j_min = j;
			}
			else
			{//已有最小值,则比较覆盖
				if (vec_ref[cur_pos] < v_min)
				{
					v_min = vec_ref[cur_pos];
					j_min = j;
				}
			}
		}

		//断言:未找到最小值
		if (!b_inited_min)
		{
			return vector<T>();
		}

		//最小数组当前位置加1
		++arr_pos[j_min];

		if (i == vec_target[i_target])
		{
			ret.push_back(v_min);
			if (++i_target >= vec_target.size())
				return ret;
		}
	}

	return ret;
}

//寻找中位数
template<typename T>
vector<T> FindMedian(const vector<vector<T>*>& all_vec)
{
	//没有数组,返回错误
	if (all_vec.empty())
		return vector<T>();

	//计算总长度
	int m = 0;
	for (auto& iter : all_vec)
	{
		m += iter->size();
	}

	//奇数
	if (m & 1)
	{
		return FindSortedArrays(all_vec, { m / 2 }, m);
	}
	else
	{
		return FindSortedArrays(all_vec, { m / 2 - 1, m / 2 }, m);
	}
}

int main()
{
	{
		cout << "-----------------------------------All arrays------------------------------" << endl;
		vector<int> vecs[] = {
			{1,2,5,11,14,99,105},
			{3,6,8,9,12,13},
			{2,107,113,120,999},
			{-1, 117}
		};

		//合并并排序
		vector<int> vecs_add;
		for (auto& iter : vecs)
		{
			//打印
			for (auto& iter1 : iter)
			{
				cout << iter1 << " ";
			}
			cout << endl;

			vecs_add.insert(vecs_add.end(), iter.begin(), iter.end());
		}
		sort(vecs_add.begin(), vecs_add.end());

		//打印合并之后
		cout << "-----------------------------------After the merger is------------------------------" << endl;
		for (auto& iter1 : vecs_add)
		{
			cout << iter1 << " ";
		}
		cout << endl;

		//
		vector<vector<int>*> vec_temp;
		for (auto& iter : vecs)
			vec_temp.push_back(&iter);

		vector<int> vec_find = { 0,5,10,12 };//要寻找的位置
		auto ret0 = FindSortedArrays(vec_temp, vec_find);

		//打印查找结果
		cout << "-----------------------------------Find results------------------------------" << endl;
		for (int i = 0; i != ret0.size(); ++i)
		{
			cout << "index " << vec_find[i] << " is " << ret0[i] << endl;
		}

		//
		auto ret1 = FindMedian(vec_temp);
		//打印中位数
		cout << "-----------------------------------Median is------------------------------" << endl;
		if (ret1.size() == 1)
		{
			cout << ret1[0] << endl;
		}
		else if (ret1.size() == 2)
		{
			//转为小数
			cout << float(ret1[0] + ret1[1]) * 0.5f << endl;
		}
		else
		{
			cout << "error" << endl;
		}
	}

	cout << "================================================================================" << endl;
	{
		cout << "-----------------------------------All arrays------------------------------" << endl;
		vector<int> vecs[] = {
			{5,6,7}
		};

		//合并并排序
		vector<int> vecs_add;
		for (auto& iter : vecs)
		{
			//打印
			for (auto& iter1 : iter)
			{
				cout << iter1 << " ";
			}
			cout << endl;

			vecs_add.insert(vecs_add.end(), iter.begin(), iter.end());
		}
		sort(vecs_add.begin(), vecs_add.end());

		//打印合并之后
		cout << "-----------------------------------After the merger is------------------------------" << endl;
		for (auto& iter1 : vecs_add)
		{
			cout << iter1 << " ";
		}
		cout << endl;

		//
		vector<vector<int>*> vec_temp;
		for (auto& iter : vecs)
			vec_temp.push_back(&iter);

		vector<int> vec_find = { 1, 3 };//要寻找的位置
		auto ret0 = FindSortedArrays(vec_temp, vec_find);

		//打印查找结果
		cout << "-----------------------------------Find results------------------------------" << endl;
		for (int i = 0; i != ret0.size(); ++i)
		{
			cout << "index " << vec_find[i] << " is " << ret0[i] << endl;
		}

		//
		auto ret1 = FindMedian(vec_temp);
		//打印中位数
		cout << "-----------------------------------Median is------------------------------" << endl;
		if (ret1.size() == 1)
		{
			cout << ret1[0] << endl;
		}
		else if (ret1.size() == 2)
		{
			//转为小数
			cout << float(ret1[0] + ret1[1]) * 0.5f << endl;
		}
		else
		{
			cout << "error" << endl;
		}
	}

	cout << "================================================================================" << endl;
	{
		cout << "-----------------------------------All arrays------------------------------" << endl;
		vector<int> vecs[] = {
			{5,6,7},
			{1,1,1,1,2,2,2,5,6,7},
			{9,11,100}
		};

		//合并并排序
		vector<int> vecs_add;
		for (auto& iter : vecs)
		{
			//打印
			for (auto& iter1 : iter)
			{
				cout << iter1 << " ";
			}
			cout << endl;

			vecs_add.insert(vecs_add.end(), iter.begin(), iter.end());
		}
		sort(vecs_add.begin(), vecs_add.end());

		//打印合并之后
		cout << "-----------------------------------After the merger is------------------------------" << endl;
		for (auto& iter1 : vecs_add)
		{
			cout << iter1 << " ";
		}
		cout << endl;

		//
		vector<vector<int>*> vec_temp;
		for (auto& iter : vecs)
			vec_temp.push_back(&iter);

		vector<int> vec_find = { 1, 3 };//要寻找的位置
		auto ret0 = FindSortedArrays(vec_temp, vec_find);

		//打印查找结果
		cout << "-----------------------------------Find results------------------------------" << endl;
		for (int i = 0; i != ret0.size(); ++i)
		{
			cout << "index " << vec_find[i] << " is " << ret0[i] << endl;
		}

		//
		auto ret1 = FindMedian(vec_temp);
		//打印中位数
		cout << "-----------------------------------Median is------------------------------" << endl;
		if (ret1.size() == 1)
		{
			cout << ret1[0] << endl;
		}
		else if (ret1.size() == 2)
		{
			//转为小数
			cout << float(ret1[0] + ret1[1]) * 0.5f << endl;
		}
		else
		{
			cout << "error" << endl;
		}
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值