LeetCode笔记-04-寻找两个正序数组的中位数

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

中位数

中位数(Median)又称中值,统计学中的专有名词,是按顺序排列的一组数据中居于中间位置的数,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。

解题

现在有两种思路:
1.将两个数组合并成一个有序数组,合并后找到中位数;
2.在两个数组中遍历,找到中位数;因为合并后的数组是一个有序(升序)的数组,则通过二分查找更为快捷,这里就先使用遍历了。

思路1解题过程

class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		vector<int> merge_nums;
		merge(merge_nums, nums1, nums2);
		double middle = 0;
		if ((merge_nums.size() % 2) == 0) {
			middle = merge_nums[merge_nums.size() / 2] / 2.0 + merge_nums[merge_nums.size() / 2 + 1] / 2.0;
		}
		else {
			middle = merge_nums[merge_nums.size() / 2];
		}
		return middle;
	}

	void merge(vector<int>& resutl, vector<int>& nums1, vector<int>& nums2) {
		auto it_nums1 = nums1.begin();
		auto it_nums2 = nums2.begin();

		while (it_nums1 != nums1.end() && it_nums2 != nums2.end()) {
			if (*it_nums1 < *it_nums2) {
				resutl.push_back(*it_nums1);
				resutl.push_back(*it_nums2);
			}
			else {
				resutl.push_back(*it_nums2);
				resutl.push_back(*it_nums1);
			}
			it_nums1++;
			it_nums2++;
		}
		if (it_nums1 != nums1.end()) {
			resutl.insert(resutl.end(), it_nums1, nums1.end());
		}
		if (it_nums2 != nums2.end()) {
			resutl.insert(resutl.end(), it_nums2, nums2.end());
		}
	}
};

在这里插入图片描述
仔细审查代码后发现:
1.while循环中两个迭代器的增加有问题,每次循环只能放入一个值;
2.计算中位数时,偶数的中位数计算有问题,不应是a[size/2]+a[size/2+1]而是a[size/2]+a[size/2-1]

class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		vector<int> merge_nums;
		merge(merge_nums, nums1, nums2);
		double middle = 0;
		if ((merge_nums.size() % 2) == 0) {
			middle = merge_nums[merge_nums.size() / 2] / 2.0 + merge_nums[merge_nums.size() / 2 - 1] / 2.0;
		}
		else {
			middle = merge_nums[merge_nums.size() / 2];
		}
		return middle;
	}

	void merge(vector<int>& resutl, vector<int>& nums1, vector<int>& nums2) {
		auto it_nums1 = nums1.begin();
		auto it_nums2 = nums2.begin();

		while (it_nums1 != nums1.end() && it_nums2 != nums2.end()) {
			if (*it_nums1 < *it_nums2) {
				resutl.push_back(*it_nums1);
				it_nums1++;
			}
			else {
				resutl.push_back(*it_nums2);
				it_nums2++;
			}
		}
		if (it_nums1 != nums1.end()) {
			resutl.insert(resutl.end(), it_nums1, nums1.end());
		}
		if (it_nums2 != nums2.end()) {
			resutl.insert(resutl.end(), it_nums2, nums2.end());
		}
	}
};

修改后可以通过;
在这里插入图片描述

思路2解题过程

1.维护两个迭代器遍历两个vector
2.首先判断参与中位数计算的操作数的格式,为偶数个还是奇数个;
3.调用函数findMedian根据中位数位置,遍历两个数组,找到指定位置的值;同时将两个迭代器中的较小值的迭代器向后移动,方便在第二次调用函数findMedian时,定位第二个操作数的值;

class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		bool is_even_numbers = (nums1.size() + nums2.size()) % 2 == 0;
		int pos_first = is_even_numbers ? (nums1.size() + nums2.size()) / 2 - 1 : (nums1.size() + nums2.size()) / 2;

		auto it_num1 = nums1.begin();
		auto it_num2 = nums2.begin();
		int count = 0;

		if (is_even_numbers) {
			double first = findMedian(nums1, nums2, it_num1, it_num2, pos_first, count);
			double secode = findMedian(nums1, nums2, it_num1, it_num2, pos_first + 1, ++count);
			return (first + secode) / 2.0;
		}
		else {
			return findMedian(nums1, nums2, it_num1, it_num2, pos_first, count);
		}
	}
	double findMedian(vector<int>& nums1, vector<int>& nums2,
		vector<int>::iterator& it1, vector<int>::iterator& it2, int pos, int& count) {
		double result = 0;
		while (it1 != nums1.end() && it2 != nums2.end()) {
			if (count == pos) {				
				if (*it1 < *it2) {
					result = *it1;
					++it1;					
				}
				else {
					result = *it2;
					++it2;
				}
				return result;
			}
			*it1 < *it2 ? ++it1 : ++it2;
			++count;
		}
		while (it1 != nums1.end()) {
			if (count == pos) {		
				result = *it1;
				++it1;
				return result;
			}
			++it1;
			++count;
		}
		while (it2 != nums2.end()) {
			if (count == pos) {
				result = *it2;
				++it2;
				return result;
			}
			++it2;
			++count;
		}
		return result;
	}
};

在这里插入图片描述
以上代码还可以改进为在某一个数组遍历结束时,中位数剩余的计数直接在剩余数组内的迭代器偏移计算得到,不再使用循环;

if (it1 != nums1.end()) {	
	it1 = it1 + pos - count;
	count = pos;
	result = *it1;
	++it1;
}

思路3解题过程

根据两个数组个数和是否为偶数,可以得到参与中位数计算的操作数是一个还是两个;
使用left和right,记录相邻的两个数值,如果是偶数个就(left + right)/2.0;如果是奇数就是right
使用迭代器:

class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		int left = 0;
		int right = 0;
		int end = (nums1.size() + nums2.size()) / 2;

		auto it1 = nums1.begin();
		auto it2 = nums2.begin();
		int count = 0;
		while (count <= end) {
			left = right;
			if (it1 != nums1.end() && (it2 == nums2.end() || *it1 < *it2))
			{
				right = *it1;
				it1++;
			}			
			else {
				right = *it2;
				it2++;
			}
			count++;
		}
		return  (nums1.size() + nums2.size()) % 2 == 0 ? (left + right) / 2.0 : right;
	}
};

使用下标索引

class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		int left = 0;
		int right = 0;
		int end = (nums1.size() + nums2.size()) / 2;

		int pos_nums1 = 0;
		int pos_nums2 = 0;
		for (int i = 0; i <= end; i++) {
			left = right;
			if (pos_nums1 < nums1.size() 
				&& (pos_nums2 >= nums2.size() || nums1[pos_nums1] < nums2[pos_nums2]))
			{
				right = nums1[pos_nums1++];
			}
			else {
				right = nums2[pos_nums2++];
			}
		}

		return ((nums1.size() + nums2.size()) & 1) == 0 ? (left + right) / 2.0 : right;
	}
};

思路4解题过程

考虑一种特殊的情况,即一个数组内的数值都比另外一个数组的最大值还要大。那么根据两个数组的长度,直接定位到中位数所在的位置;
不考虑以上的特殊情况时,找中位数就是找找到合并数组后位于中间位置的数或者两数之和;如果不考虑合并,那么就需要从两个数组中去找第len/2位置的数;之前的在两个数组的遍历都是在寻找这个第len/2数;另外也可以换种思路,去排除前len/2-1个数;
LeetCode的官方给出第一个解题思路就是二分法查找;
其中有这么一句话:如何把时间复杂度降低到 O(log(m+n)) 呢?如果对时间复杂度的要求有 log,通常都需要用到二分查找,这道题也可以通过二分查找实现。
二分法是指要排除的前k-1个数,分为两部分,用来部分的排除;
如果我们要找到第k个数,现在有两个有序的集合,那我们就会先看看每个集合中k/2-1位置的数相比较,排除较小的数以及前面的所有数,这样就可以排除k/2个数据,然后再在两个集合中对比k/2/2-1位置的数,依次排除;到最后将排除k-1个数,那么就会找到第k个数了。

class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		int total_len = nums1.size() + nums2.size();
		if (total_len % 2 == 1) {
			return getKthElement(nums1, nums2, (total_len + 1) / 2);
		}
		else {
			return (getKthElement(nums1, nums2, (total_len + 1) / 2)
				+ getKthElement(nums1, nums2, total_len / 2)) / 2.0;
		}
	}
	int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {
		int m = nums1.size();
		int n = nums2.size();
		int index1 = 0, index2 = 0;

		while (true)
		{
			// 当nums1集合以遍历完时,直接从nums2中找到K位置的值;
			if (index1 == m) {
				return nums2[index2 + k - 1];
			}
			// 当nums2集合以遍历完时,直接从nums1中找到K位置的值;
			if (index2 == n) {
				return nums1[index1 + k - 1];
			}
			// 当K收缩到1时,从两个当前位置找一个最小值返回;
			if (k == 1) {
				return min(nums1[index1], nums2[index2]);
			}

			int next_index1 = min(index1 + k / 2 - 1, m - 1);
			int next_index2 = min(index2 + k / 2 - 1, n - 1);
			// 当k大于1时
			if (nums1[next_index1] <= nums2[next_index2]) {
				k -= next_index1 - index1 + 1;
				index1 = next_index1 + 1;
			}
			else {
				k -= next_index2 - index2 + 1;
				index2 = next_index2 + 1;
			}
		}
	}
};

参考资料

1.寻找两个有序数组的中位数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

黑山老妖的笔记本

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

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

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

打赏作者

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

抵扣说明:

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

余额充值