在两个⻓度相等的排序数组中找到上中位数

问题描述

给定2个有序数组arr1和arr2,已知2个数组的长度都是N,求2个数组中所有数的上中位数up_mid_val。要求时间复杂度为O(logN),空间复杂度O(1)

举例

arr1 = [1, 2, 3, 4], arr2 = [3, 4, 5, 6].
上中位数up_mid_val:3
arr1 = [0, 1, 2], arr2 = [3, 4, 5]
上中位数up_mid_val:2

解答

用递归解决。
递归的核心思想是在保证子问题性质相同的情况下不断缩小问题的规模。那么现在先分析如何从原问题得到子问题(f(n)—>f(n-1))

原问题—>子问题

看到“有序数组”,首先想到的是“二分查找”中的中间位置,即:

mid = begin + (end - begin) / 2;

很容易发现,当两个数组的中间值相同时,不论N为奇数还是偶数,2者的中间值即为2者的上中位数up_mid_val:

if(arr1[mid] == arr2[mid]){
	up_mid_val = arr1[mid]
}

因为中间值正好可以把数组一分为二,那么现在自然就在想:能不能用中间值缩小问题的规模?答案是可以的,现在按照N为偶数Or奇数的情况分类讨论:

  • N为偶数
    在此处 mid == N/2 - 1。如果arr1[mid] < arr2[mid](反过来也一样),那么可以断定up_mid_val只会出现在arr1的后半部分(arr1[mid + 1, …])和arr2的前半部分(arr2[0, …, mid])。
    原因:由于arr1[mid] < arr2[mid],所以arr1和arr2的所有元素在整合为一个有序数组后,元素中小于等于arr1[0…mid]中元素的个数一定小于(N - 1),那么up_mid_val不可能在该区域内出现,不仅如此,arr1[0, … , mid]中的元素一定出现在up_mid_val的左边。同理也可知道up_mid_val不可能出现于arr2[mid + 1, …],并且所有元素均出现在up_mid_val的右边。所以up_mid_val只有可能在arr1[mid + 1, …]和arr2[0, …, mid]中出现。
    问题缩小:因为up_mid_val在N为偶数时将arr1和arr2整合之后的数组分成了元素个数为N - 1(左边)和元素个数为N(右边)的对称的两部分,又从刚刚的讨论可知已有mid + 1个元素分别可以确定处于up_mid_val的左边和右边,那么由对称性,剩余的2部分:arr1[mid + 1, …]、arr2[0, …, mid]直接构成了子问题。

  • N为奇数
    在此处 mid == N/2,正好将数组划分为左右对称的两部分。如果arr1[mid] < arr2[mid](反过来也一样),那么可以断定up_mid_val只会出现在arr1的后半部分(arr1[mid, …],注意包含arr1[mid])和arr2的前半部分(arr2[0, …, mid - 1])。
    原因:同理
    问题缩小:大部分与偶数情况相同,可类推,但是有一点需要注意。在这个地方的子问题不是直接由arr1[mid, …]和arr2[0, …, mid - 1]构成的。因为这两者所含有的元素个数不同,一个是N/2 + 1个,一个是N/2个。之所以会出现这种情况是因为我们可以确定整合之后的数组中值小于等于arr2[mid]的元素的个数至少为N个,所以直接将其排除于候选up_mid_val之外。但是为了使用递归,在不改变对称性的情况下,我们将arr2[mid]纳入子问题,也就是说子问题由arr1[mid, …]和arr2[0, …, mid - 1]构成。

结束条件

  • 数组为空
    返回-1即可

  • 数组只含有1个元素
    直接返回较小值

代码如下(直接从《帅的玩编程》上面copy的JAVA代码)

public static int getUpMedian(int[] arr1, int[] arr2) {
	if(arr1 == null || arr2 == null )
 		return -1;
 	// 开始寻找
 	return find(arr1, 0, arr1.length - 1, arr2, 0, arr2.length - 1);
}
public static int find(int[] arr1, int l1, int r1, int[] arr2, int l2, int
r2) {
	int mid1 = l1 + (r1 - l1) / 2;
	int mid2 = l2 + (r2 - l2) / 2;
	// 表示数组只剩下⼀个数,把两个数组中较⼩的数返回去
	if (l1 >= r1) {
		return Math.min(arr1[l1], arr2[l2]);
	}
	// 元素个数为奇数,则offset为0,为偶数则 offset 为 1
 	int offset = ((r1 - l1 + 1) & 1) ^ 1;// ⽤位运算⽐较快
 	if (arr1[mid1] < arr2[mid2]) {
 		return find(arr1, mid1+offset, r1, arr2, l2, mid2);
 	} else if (arr1[mid1] > arr2[mid2]) {
 		return find(arr1, l1, mid1, arr2, mid2 + offset, r2);
 	} else {
 		return arr1[mid1];// 返回 arr2[mid2]也可以。
 	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值