力扣-4-寻找两个正序数组的中位数

题目要求O(log (m+n))的时间复杂度

知道了两个数组的长度,那么中位数的下标以及如何计算是可以确定的,给出的是两个正序数组,如果使用双指针,从两个数组头开始扫描并比较,找出合并后第 K 小的数字,时间复杂度是多少?

时间复杂度是O((M+N)/2),这个目标还不及题目的要求,看到logN就会想到二分,但是以往的二分是在有序数组中查找指定元素,这里是查找合并后的中位元素,或者说查找合并后第 K 小的元素,似乎不太一样

官方二分

这种思路的精髓在于:每一次循环都能排除掉当前比较中较小的那部分元素,从而在不断缩小的范围内寻找到中位数,而不需要对整个数组进行排序

这个二分思路中 k 的更新规则是什么,以及循环退出条件是什么?

k 的更新规则是每次循环结束后减去排除掉的元素数量,以更新我们在剩下的数组中查找新的第k小的元素

为什么这么做是正确的呢?

因为我们只会从数组头排除,也就是排除小的那一部分;结束条件是k=1,即我们需要第一小的元素,就是当前指针指向的两个元素。
但是很明显就算当k=1时,也有两个指针指向了两个数字,我们选哪一个呢?为什么选较小的那个作为中位数是正确的?
因为我们需要第一小的元素,就是更小的那一个

这个二分思路跟常规二分不一样的地方在于:首先它只会排除左边的部分,但是是两个数组中的某一个左半部分,用两个指针来实现。

我想这个思路可能更像是:查找合并数组后第 k 小的元素,更像是转化为 -> 排除掉合并后数组中最小的 k-1 个元素,并且每次排除 k/2 个元素,我觉得这样解释这个思路更能让人理解

如果能够理解这个思路了,那么就可以尝试自己写

指针越界问题

为什么指针下标定位 k/2-1 而不是 k/2 ?因为第 k 小的元素对应的下标是 k-1。已知当两个数组都不为空时长度至少为 2,这时候 k/2 就会出问题,因为如果两个数组长度都为 1 最多下标只到 0,而 k 可以取 2

但是比如k=7,k/2=3正好就对应了中间元素的下标,同时比如一个数组长度为 1,另一个很长 13,k=7,这时候就算 k/2-1 也无法避免在第一个数组中的越界问题,这个如何解决呢?所以取下标之前要对数组长度做判断

	double getKth(vector<int>& nums1, vector<int>& nums2, int k) {
		int m = nums1.size(), n = nums2.size();
		// 需要考虑两个数组中存在空数组的情况
		// 因为m+n>=1,所以不考虑同时为空
		if (m == 0)return nums2[k - 1];
		if (n == 0)return nums1[k - 1];
		int index1 = k / 2 - 1, index2 = k / 2 - 1, offset;
		// 处理越界的情况,第一次初始化索引时有可能会越界
		if (index1 > m - 1)return nums2[n - m - 1];
		if (index2 < n - 1) return nums1[m - n - 1];
		while (k > 1) {
			offset = k / 2;
			k = k - offset;// 这里不能直接/2,否则奇数情况会丢一个
			if (nums1[index1] > nums2[index2]) {
				// 更新两个索引
				// 每一次更新索引,向右移动偏移的索引下标也有可能越界
				index1 = k / 2 - 1;
				index2 = index1 + offset;
				if (index2 > n - 1)return nums1[m - offset - 1];
			}
			else {
				index2 = k / 2 - 1;
				index1 = index2 + offset;
				if (index1 > m - 1) return nums2[n - offset - 1];
			}
		}
		return min(nums1[index1], nums2[index2]);
	}

尝试……但是上面肯定是错的,因为 k、offset、m、n 的关系我理不清,更新 index 并处理越界那里肯定是错的

官方题解

首先 index 都初始化为 0,并把 index 的初始化和更新统一,并放在了循环内部
并且当index==数组长度作为循环退出条件之一
这样巧妙在:

  1. 统一处理了其中一个数组为空的情况
  2. 统一处理了 index 的初始化和更新
  3. 统一处理了当更新偏移量的索引后索引越界的问题

if (index1 == m) return nums2[index2 + k - 1];这一句可以保证当初始化后数组为空的正确性,但是怎么保证下标越界情况的正确性?
这个索引更新策略的正确性又如何证明?int newIndex1 = min(index1 + k / 2 - 1, m - 1);

每次都加上 k/2-1,第一次肯定是正确的,后面更新又怎么证明?

index 和 newIndex 分别分别代表了什么?它们之间什么关系?代表了什么意义?

newIndex 很明显是每一次更新后的索引,比较时用的也是这个索引,那么为什么要保存旧的index,甚至还要去更新它?
每一次 newIndex 的更新依赖的不是上一次 newindex 而是+newIndex+1后的 index,其中newIndex+1代表的就是偏移量

如何证明min(index1 + k / 2 - 1, m - 1)这个更新策略的正确性?

当跟新后的下标越界就取末尾元素,我觉得这样可能也是为什么用 newIndex+1 替换 k/2 作为偏移量的原因
当上一次下标越界只能取末尾元素时,偏移量是小于 k/2 的,具体是多少呢?就是整个数组的长度,不用再考虑之前的偏移累加
所以说== index 代表的其实是偏移累加,而之所以要用 newIndex+1 替换 k/2 作为偏移量的原因是因为越界情况会导致可偏移量是小于 k/2 的

我本来是这么写的,但是后面遇到[1,2] [3,4]这个测试用例的时候我发现,k1 的条件必须写到循环里面==,否则会出现越界

	double getKth(vector<int>& nums1, vector<int>& nums2, int k) {
		int m = nums1.size(), n = nums2.size();
		int index1 = 0, index2 = 0;
		while (k > 1) {
			if (index1 == m) return nums2[index2 + k - 1];
			if (index2 == n)return nums1[index1 + k - 1];

			int newIndex1 = min(index1 + k / 2 - 1, m - 1);
			int newIndex2 = min(index2 + k / 2 - 1, n - 1);

			if (nums1[newIndex1] > nums2[newIndex2]) {
				k -= newIndex2 - index2 + 1;
				index2 = newIndex2 + 1;
			}
			else {
				// 当索引不越界的时候,下面这句和k -= k / 2;是等价的
				k -= newIndex1 - index1 + 1;
				index1 = newIndex1 + 1;
			}
		}
		return min(nums1[index1], nums2[index2]);
	}

能不能把两句越界判断提出来?
不行,因为这两句同时承担了第一次判空,所以结论是最后一句不能写到外面来

官解这代码我一点都改不动

	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		int len = nums1.size() + nums2.size();
		if (len & 1)return getKth(nums1, nums2, (len + 1) / 2);
		else return (getKth(nums1, nums2, len / 2) + getKth(nums1, nums2, len / 2 + 1)) / 2.0;
	}

	double getKth(vector<int>& nums1, vector<int>& nums2, int k) {
		int m = nums1.size(), n = nums2.size();
		int index1 = 0, index2 = 0;
		while (true) {
			if (index1 == m) return nums2[index2 + k - 1];
			if (index2 == n)return nums1[index1 + k - 1];

			if (k == 1)return min(nums1[index1], nums2[index2]);

			int newIndex1 = min(index1 + k / 2 - 1, m - 1);
			int newIndex2 = min(index2 + k / 2 - 1, n - 1);

			if (nums1[newIndex1] > nums2[newIndex2]) {
				k -= newIndex2 - index2 + 1;
				index2 = newIndex2 + 1;
			}
			else {
				// 当索引不越界的时候,下面这句和k -= k / 2;是等价的
				k -= newIndex1 - index1 + 1;
				index1 = newIndex1 + 1;
			}
		}
	}
posted @ 2023-08-20 14:18  YaosGHC  阅读(16)  评论(0编辑  收藏  举报