暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【算法LeetCode】第四题 - 寻找两个正序数组的中位数(困难)

半猫Coder 2021-01-06
481

SQ太甜了WWWWW

看到可爱的秋瞳,你充满了决心




问题描述:

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


进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?



来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。



一、合并数组(即暴力解法)

  • 思路及算法:

对目前的我来说,实在想不到其他办法了。当然我知道合并数组这种做法非常“蠢”,因为时间复杂度会很大。
首先通过new一个新的数组list,list的长度为nums1和nums2的长度之和len。
然后通过一个循环,以较短的数组长度为界按序逐个将两个数组的元素放入新数组list中。此时,较长的数组中仍有元素未放入,因此,在下面还需要进行判断,判断哪个数组没有放完,并通过循环将剩下的元素放入其中。
最后就是求中位数。因为存在两种情况—— list长度为奇数或者偶数。
当长度为奇数时,中位数就是 len/2 这个位置上的元素。因为当整型数据相除时,结果商自动舍弃小数部分,因此相当于向下取整,而数组的元素从0开始排序,自然中位数就是 len/2 这个结果的整数部分了。
当长度为偶数时,需要将中间两个数相加后除以2。而中间两个数一个是len/2,另一个是 (len/2)-1。此时要注意,计算时需要将整形数组的元素强制转化为double类型的数据,结果才是正确的(万分注意!)

  • 代码:

class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len = nums1.length+nums2.length;
int[] list = new int[len];
int m = 0, i=0, j=0;
while(i<nums1.length && j<nums2.length) {
if(nums1[i]<nums2[j]){
list[m++] = nums1[i++];
} else {
list[m++] = nums2[j++];
}
}
if(i != nums1.length){
for( ; i<nums1.length; i++){
list[m++] = nums1[i];
}
} else {
for( ; j<nums2.length;j++){
list[m++] = nums2[j];
}
}
        
double count=0;
if(len%2!=0){
count = list[len/2];
return count;
} else {
count = ((double)list[len/2]+(double)list[len/2-1])/2;
return count;
}
}
}


  • 复杂度分析:

时间复杂度:遍历两数组O(m+n)
空间复杂度:O(m+n) (应该是吧?)



二、二分查找

  • 思路及算法:

如果对时间复杂度的要求有log,通常都需要用到二分查找。不过虽然我看过了题解,可是仍旧搞不太明白这个方法的逻辑和想法。现在就先把它记录下来,以后有能力了再回来看一遍吧!

中位数:在只有一个有序数组的时候,中位数把数组分割成两个部分。
根据定义,分数组长度为奇数和偶数讨论。
数组长度为偶数时,中位数有两个,其中一个是左边数组的最大值,另一个是右边数组的最小值。
数组长度为奇数时,中位数有1个,我们把中位数分到左边数组,即左边数组多一个数,而它就是中位数。

中位数:在有两个有序数组的时候,仍然可以把两个数组分割成两个部分。
我们使用一条分割线把两个数组分别分割成两个部分:


1、红线左边和右边的元素个数相等,或者左边元素的个数比右边元素的个数多1个;
2、红线左边的所有元素的数值 <= 红线右边的所有元素的数值;
那么中位数就一定只与红线两侧的元素有关,确定这条红线的位置使用二分查找

分割线左边5个元素,分割线右边4个元素
当两个数组的元素个数之和为奇数的时候,有:size(left) = size(right)+1

分割线左边5个元素,分割线右边5个元素
当两个数组的元素个数之和为偶数的时候,有:size(left) = size(right)

假设数组1的长度为m,数组2的长度为n。
当 m+n 为偶数的时候,size(left) = (m+n)/2 = (m+n+1)/2;(整数除法向下取整,因此两个结果是一样的)
当 m+n 为奇数的时候,由于之前假设中位数被分到左边,size(left) = (m+n+1)/2;
我们可以把以上两种情况合并:size(left) = (m+n+1)/2 。
得到这个结论的好处是,不用分奇偶数讨论,只需要确定其中一个数组的分割线位置,另一个数组的分割线位置可以通过公式计算出来。

接下来看中位数要保证的第二个条件:红线左边的所有元素数值 <= 红线右边的所有元素数值。
由于两个数组都是有序数组,在同一个数组内,分割线一定满足左边的所有元素小于等于右边的所有元素。
在不同的数组之间,应该保证交叉小于等于关系成立,如下图:

那么,只要不符合交叉小于等于关系,我们就需要适当调整分割线位置。

不符合要求的分割线:
示例1:

此时分割线左右两侧元素的个数是符合要求的,但是第2个数组左边的最大值大于第1个数组右边的最小值,因此这条分割线不符合要求。
中位数分割线右边的数太小了。
调整方案:将中位数分割线在数组1的位置右移。

示例2:

第1个数组左边位置的最大值,大于第2个数组右边位置的最小值。
中位数分割线左边的数太大了。
调整方案:将中位数分割线在数组1的位置左移。

极端情况:
a、第1个数组在分割线右侧没有元素
b、第1个数组在分割线左侧没有元素
由于我们需要通过访问“中间数分割线”左右两边的元素,因此应该在较短的数组上确定“中间数分割线”的位置。

c、第1个数组在分割线右侧没有元素并且第2个元素在分割线左侧没有元素
d、第1个数组在分割线右侧没有元素并且第2个元素在分割线左侧没有元素

  • 代码:

class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}


int m = nums1.length;
int n = nums2.length;
int left = 0, right = m;
// median1:前一部分的最大值
// median2:后一部分的最小值
int median1 = 0, median2 = 0;


while (left <= right) {
// 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
// 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
int i = (left + right) / 2;
int j = (m + n + 1) / 2 - i;


// nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
int nums_im1 = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]);
int nums_i = (i == m ? Integer.MAX_VALUE : nums1[i]);
int nums_jm1 = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]);
int nums_j = (j == n ? Integer.MAX_VALUE : nums2[j]);


if (nums_im1 <= nums_j) {
median1 = Math.max(nums_im1, nums_jm1);
median2 = Math.min(nums_i, nums_j);
left = i + 1;
} else {
right = i - 1;
}
}


return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
}
}


// 作者:LeetCode-Solution
// 链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


(WRO,完全搞不懂,我讨厌数学!!


  • 复杂度分析:

时间复杂度:O(log min(m,n)),其中m和n分别是数组nums1和nums2的长度,查找的区间是[0,m],而该区间的长度在每次循环之后都会减少为原来的一半。所有只需要执行 log m 次循环。由于每次循环中的操作次数是常数,所以时间复杂度为 O(log m) 。由于我们可能需要交换nums1和nums2使得 m<=n ,因此时间复杂度是 O(log min(m,n))。
空间复杂度:O(1)



文章转载自半猫Coder,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论