《[04] 寻找两个正序数组的中位数》
《LeetCode:[04] 寻找两个正序数组的中位数》
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O (log (m+n)) 。
示例 1:1
2
3输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:1
2
3输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
方法签名1
2public double findMedianSortedArrays(int[] nums1, int[] nums2) {
}
解题思路历程
看完这个问题你想到了什么?若是思路没有问题应该是二分搜索算法,但传统的二分搜索是针对一个已经排序的数组,但这里有两个已经排序的数组,那应该怎么处理呢?
最直观的思路:将两个已排序的数组合并为一个已经排序的数组,然后使用二分搜索算法搜索结果。
将两个排序数组合并为一个排序数组
可以非常明显的看出答案是 4
,但是两个有序数组合并的时间复杂度 O(m+n)
,已经不符合 O(log (m+n))
的要求。
那么如何在不合并的情况下找出这个结果呢?我们找一下规律,其实就是 nums1
数组中有一个 i
将数组 nums1
分成两半, nums2
数组中有一个 j 将数组 nums2
分成两半。数组 nums1 和 nums2 的前一半数量等于两个数组数量和(m+n)
的一半,结果向上取整。 数组 nums1
和 nums2
的前一半数据的最大值小于等于后一半数据的最小值。
i 和 j 切分后的数组
我们将合并后的数组重新拆开,发现一个规律,只要找到一个合适的 **i
** 和 **j
** 满足以下情况,如上图。
$$
i+j=(m+n+1)/2
$$
$$
nums1[i-1]<=nums2[j]
$$
$$
nums[j-1]<=nums1[i]
$$
最终,这个问题就可以转换成在较小的那个数组上进行二分查找,需要找出一个 **i
** 满足如下条件。
$$
j=(m+n+1)/2 -i
$$
$$
nums1[i-1]<=nums2[j]
$$
$$
nums[j-1]<=nums1[i]
$$
解题步骤:
理解问题:
- 目标是找到两个已排序数组的中位数。
- 假设数组
nums1
和nums2
都不为空,并且它们的总长度是m+n
。
检查特殊情况:
- 如果一个数组为空,中位数就是另一个数组的中位数。
- 如果两个数组的总长度是偶数,中位数是中间两个数的平均值;如果是奇数,则是中间的那个数。
二分搜索法:
使用二分搜索找到合适的切分位置。这意味着在
nums1
和nums2
中找到两个位置i
和j
,使得
$$
nums1[i-1]<=nums2[j]
$$$$
nums1[i-1]<=nums2[j]
$$这个过程需要不断调整
i
和j
以满足上述条件,同时保证
$$
i+j=(m+n+1)/2
$$
计算中位数:
一旦找到合适的 i 和 j,中位数可以通过比较
nums1 [i-1]、nums1 [i]、nums2 [j-1] 和 nums2 [j] 来确定。
如果总长度是奇数,则中位数是
$$
max(nums1[i-1],nums2[j-1])
$$如果总长度是偶数,则中位数是
$$
(max(nums1[i-1],nums[j-1])+max(nums1[i],nums[j]))/2
$$
边界条件处理:
- 在二分搜索过程中,需要考虑数组边界情况。
- 例如,当
i
为0
或n
时,应该相应地处理nums1[i-1]
或nums1[i]
。
时间复杂度分析:
此算法的时间复杂度通常是 O(log(min(m, n)))
,其中 m
和 n
是两个数组的长度。
实现这个算法时,关键在于理解如何通过二分搜索在两个数组中找到正确的切分点,以及如何处理边界情况。
解题步骤
选择较短的数组进行二分查找:为了优化性能,总是在两个数组中较短的那个上执行二分查找。
二分查找的应用:使用二分查找找到一个切分点,这个切分点将两个数组划分为左右两部分,使得左边所有元素都小于右边的所有元素。
确保切分的正确性:确保左侧部分的最大值小于或等于右侧部分的最小值。
计算中位数:
- 如果两个数组的总长度是偶数,中位数是左侧最大值和右侧最小值的平均数。
- 如果总长度是奇数,中位数是左侧的最大值。
处理边界条件:考虑数组长度为零或者切分点在数组的起始或结束位置的情况。
Java 实现
1 | public double findMedianSortedArray(int[] nums1, int[] nums2) { |
在这个实现中,我们首先确定两个数组中较短的那个,并在其上执行二分查找。
我们不断地调整切分位置,直到找到一个位置,使得左侧所有元素都小于或等于右侧所有元素。然后根据左右两侧元素的最大值和最小值计算出中位数。
这种方法的时间复杂度为 O(log(min(m, n)))
,其中 m
和 n
分别是两个数组的长度。