题目
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
提示:
nums1.length == mnums2.length == n0 <= m <= 10000 <= n <= 10001 <= m + n <= 2000-10 <= nums1[i], nums2[i] <= 10
进阶:你能设计一个时间复杂度为O(log (m+n))的算法解决此问题吗?
解题思路
1、使用归并
使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。
时间复杂度是 O(m+n),空间复杂度是 O(m+n)
2、双指针法
不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 0 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。
虽然可以将空间复杂度降到 O(1),但是时间复杂度仍是 O(m+n)
3、二分查找
如何把时间复杂度降低到 O(log(m+n)) 呢?如果对时间复杂度的要求有 log,通常都需要用到二分查找,这道题也可以通过二分查找实现。
根据中位数的定义,当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1。
由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论。
对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。
假设两个有序数组分别是 A 和 B。要找到第 k 个元素,我们可以比较 A[k/2-1] 和 B[k/2-1],其中 / 表示整数除法。由于 A[k/2-1] 和 B[k/2-1] 的前面分别有 A[0..k/2-2] 和 B[0..k/2-2],即 k/2-1 个元素,对于 A[k/2-1] 和 B[k/2-1] 中的较小值,最多只会有 (k/2-1)+(k/2-1)≤k−2 个元素比它小,那么它就不能是第 k 小的数了。
因此我们可以归纳出三种情况:
- 如果 A[k/2-1] < B[k/2-1],则比 A[k/2-1] 小的数最多只有 A 的前 k/2-1 个数和 B 的前 k/2-1 个数,即比 A[k/2-1] 小的数最多只有 k-2 个,因此 A[k/2−1] 不可能是第 k 个数,A[0] 到 A[k/2−1] 也都不可能是第 k 个数,可以全部排除。
- 如果 A[k/2−1]>B[k/2−1],则可以排除 B[0] 到 B[k/2−1]。
- 如果 A[k/2−1]=B[k/2−1],则可以归入第一种情况处理。

可以看到,比较 A[k/2−1] 和 B[k/2−1] 之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 kk 的值,这是因为我们排除的数都不大于第 kk 小的数。
有以下三种情况需要特殊处理:
- 如果 A[k/2−1] 或者 B[k/2−1] 越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少 kk 的值,而不能直接将 kk 减去 k/2。
- 如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第 k 小的元素。
- 如果 k=1,我们只要返回两个数组首元素的最小值即可。
答案
1、使用归并
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {// 结果double result = 0;// 两个 int 数组的长度int length1 = nums1.length;int length2 = nums2.length;// 两个 int 数组的长度之和为奇数或偶数 奇数为1 偶数为0int isOdd = (length1 + length2) % 2;// 中位数下标 如果长度之和为奇数,则中位数下标就为 flag ,如果为偶数,中位数为 flag 和 flag - 1 之和 / 2int flag = (length1 + length2) / 2;// 缓存数组int[] temp = new int[nums1.length + nums2.length];// 遍历下标int i = 0, j = 0, k = 0;while (i < length1 || j < length2) {// System.out.println("i: " + i);// System.out.println("j: " + j);// System.out.println("k: " + k);// System.out.println();if (i < length1 && j < length2) {// nums1 和 nums2 都未处理完// 判断 A 和 B 哪个小if (nums1[i] <= nums2[j]) {// A 小等于 Btemp[k] = nums1[i];i++;} else {// B 比 A 小temp[k] = nums2[j];j++;}// 判断是否为中位数if (isOdd == 1) {// 奇数个if (k == flag) {// 是中位数result = temp[k];break;}} else {// 偶数个if (k == flag) {// 是中位数result = (temp[k] + temp[k - 1]) / 2.0;break;}}k++;continue;}if (i < length1) {// nums1 尚未处理完 , nums2 已处理完// 插入temptemp[k] = nums1[i];i++;// 判断是否为中位数if (isOdd == 1) {// 奇数个if (k == flag) {// 是中位数result = temp[k];break;}} else {// 偶数个if (k == flag) {// 是中位数result = (temp[k] + temp[k - 1]) / 2.0;break;}}k++;continue;}if (j < length2) {// nums2 尚未处理完 , nums1 已处理完// 插入temptemp[k] = nums2[j];j++;// 判断是否为中位数if (isOdd == 1) {// 奇数个if (k == flag) {// 是中位数result = temp[k];break;}} else {// 偶数个if (k == flag) {// 是中位数result = (temp[k] + temp[k - 1]) / 2.0;break;}}k++;continue;}}// System.out.println("-----------------------------");// System.out.println("length1: " + length1);// System.out.println("length1: " + length2);// System.out.println("isOdd: " + isOdd);// System.out.println("flag: " + flag);// System.out.println(Arrays.toString(temp));return result;}}
2、双指针法
…
3、二分查找
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {// 两个数组的元素个数int length1 = nums1.length;int length2 = nums2.length;// 第 k 小值int k = (length1 + length2) / 2;if ((length1 + length2) % 2 == 1) {// 奇数个return find(nums1, nums2, k);} else {// 偶数个return (find(nums1, nums2, k) + find(nums1, nums2, k + 1)) / 2.0;}}// 求两个排序好的数组中,第 k 小的数public double find(int[] nums1, int[] nums2, int k) {// nums1 与 nums2 的游标int i = 0, j = 0;// 两个数组的元素个数int length1 = nums1.length;int length2 = nums2.length;while (true) {// 边界if (i >= length1) {return nums2[j + k - 1];}if (j >= length2) {return nums1[i + k - 1];}if (k == 1) {return Math.min(nums1[i], nums2[j]);}// 正常情况int halfK = k / 2;int newIndexI = Math.min(i + halfK, length1) - 1;int newIndexJ = Math.min(j + halfK, length2) - 1;if (nums1[newIndexI] <= nums2[newIndexJ]) {k = k - (newIndexI - i + 1);i = newIndexI + 1;} else {k = k - (newIndexJ - j + 1);j = newIndexJ + 1;}}}}
