java 两个数组交叉,leetcode 4. 寻找两个有序数组的中位数(Java版)

题目描述(题目难度,困难)

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3]

nums2 = [2]

则中位数是 2.0

示例 2:

nums1 = [1, 2]

nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

示例代码

下面是我时间复杂度为 O(log(min(m,n))) 的 Java 代码,代码虽然看起来比较长,但其实大部分是重复逻辑(^&^)。

class Solution {

public double findMedianSortedArrays(int[] nums1, int[] nums2) {

//因为题目说两数组不会同时为空,所以这种情况忽略

//有一个数组为空的情况

if(nums1 == null || nums1.length == 0)

if(nums2.length % 2 == 0) return (nums2[nums2.length/2-1]+nums2[nums2.length/2])/2.0;

else return nums2[nums2.length/2];

if(nums2 == null || nums2.length == 0)

if(nums1.length % 2 == 0) return (nums1[nums1.length/2-1]+nums1[nums1.length/2])/2.0;

else return nums1[nums1.length/2];

//两数组不存在交叉

int len = nums1.length + nums2.length;//总长度

int m = len/2;

if(nums1[nums1.length-1] <= nums2[0]){

if(len % 2 == 0){

if(m-1 > nums1.length-1) return (nums2[m-nums1.length-1]+nums2[m-nums1.length])/2.0;

else if(m <= nums1.length-1) return (nums1[m-1]+nums1[m])/2.0;

else return (nums1[m-1]+nums2[m-nums1.length])/2.0;

}else{

if(m > nums1.length-1) return nums2[m-nums1.length];

else return nums1[m];

}

}

if(nums2[nums2.length-1] <= nums1[0]){

if(len % 2 == 0){

if(m-1 > nums2.length-1) return (nums1[m-nums2.length-1]+nums1[m-nums2.length])/2.0;

else if(m <= nums2.length-1) return (nums2[m-1]+nums2[m])/2.0;

else return (nums2[m-1]+nums1[m-nums1.length])/2.0;

}else{

if(m > nums2.length-1) return nums1[m-nums2.length];

else return nums2[m];

}

}

//两数组存在交叉,使用类二分查找

int i0 = 0, j0 = 0;//i,j 分别为两数组搜索区间的起始下标

int len1 = nums1.length, len2 = nums2.length;//len1,len2 分别表示两数组搜索区间的长度

int i, j,L;

if(len % 2 == 0){//两个值m-1,m

while(true){

i = i0 + len1/2;

j = j0 + len2/2;

L = i + j +1;

if(nums1[i] < nums2[j]){//nums2[j]为大值

if(L > m){//大值指针前移

if((len2=j-j0) == 0) return (nums1[i+m-L]+nums1[i+m-L+1])/2.0;

}else{

if(L == m && (i >= nums1.length-1 || nums1[i+1] >= nums2[j])){

if(j <= 0 || nums1[i] > nums2[j-1]) return (nums1[i]+nums2[j])/2.0;

else return (nums2[j-1]+nums2[j])/2.0;

}

//小值指针后移

if((len1 -= i-i0+1) == 0) return (nums2[j+m-L]+nums2[j+m-L-1])/2.0;

i0 = i+1;

}

}else{//nums1[i]为大值

if(L > m){

if((len1 = i-i0) == 0) return (nums2[j+m-L]+nums2[j+m-L+1])/2.0;

}else{

if(L == m && (j >= nums2.length-1 || nums2[j+1] >= nums1[i])){

if(i <= 0 || nums2[j] > nums1[i-1]) return (nums1[i]+nums2[j])/2.0;

else return (nums1[i-1]+nums1[i])/2.0;

}

if((len2 -= j-j0+1) == 0) return (nums1[i+m-L]+nums1[i+m-L-1])/2.0;

j0 = j+1;

}

}

}

}else{//只有一个值

while(true){

i = i0 + len1/2;

j = j0 + len2/2;

L = i + j +1;

if(nums1[i] < nums2[j]){//nums2[j]为大值

if(L > m){//大值指针前移

if((len2 = j-j0) == 0) return nums1[i+m-L+1];

}else{

if(L == m && (i >= nums1.length-1 || nums1[i+1] >= nums2[j])) return nums2[j];

//小值指针后移

if((len1 -= i-i0+1) == 0) return nums2[j+m-L];

i0 = i+1;

}

}else{//nums1[i]为大值

if(L > m){

if((len1 = i-i0) == 0) return nums2[j+m-L+1];

}else{

if(L == m && (j >= nums2.length-1 || nums2[j+1] >= nums1[i])) return nums1[i];

if((len2 -= j-j0+1) == 0) return nums1[i+m-L];

j0 = j+1;

}

}

}

}

}

}

思路解析

首先约定符号,假设 a,b 分别为两数组 A,B 的元素,i,j 为 a,b 在各自数组的下标。

整个有序序列(两数组归并后),记为 M,先假设 M.length 为奇数

假设 a 在 M 中的实际下标为 la,那么所有小于等于 la 的下标值我们可以记为 La。这个La,我们称其为 a 在 M 中的下标下界

在 M 中的中位数下标为 target,即我们要的就是 M[target]

对于这个题目存在下述两个事实:

假设 a = max(a, b),则 La = i+j+1(注意如果 B[j+1] >= a,则la == La;如果B[j+1] < a,则la >= La)

如果 La > target,则在 A 数组中,a 后面的所有元素包括 a 都不可能是M[target]。

如果 La <= target,则在 B 数组中,b 前面的所有元素包括 b 都不可能是 M[target](这个难理解一点,给出证明)。

关于第二点第二条的证明如下:

因为 B 数组中 b 前面的元素只有 j 个,A 数组中在 b 前面的元素最多有 i 个,所以 b 在 M 中的下标最大只能是 i+j。

即 lb <= i+j < i+j+1 = La,即 lb < La

又 La <= target,所以 lb < target,b 都不可能了,b 前面的元素更不可能了。

所以当 La > target,指向 a 的指针前移。当 La <= target,指向 b 的指针后移。

那么我们可以设计一个类似二分查找的算法,移动指针的时候每次移动待搜索区间的一半。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值