LeetCode 04题 -> 寻找两个正序数组的中位数
1. 题目描述
-
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为
O(log(m+n))
。 -
示例 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
2. 解题思路
-
思路1:暴力合并数组,但是不满足时间复杂度要求
O(log(m+n))
,过程不详细解析了,见代码public static double findMedianSortedArraysS1(int[] nums1, int[] nums2) { int[] nums = new int[m+n]; int count = 0; int i = 0; int j = 0; while (i < m && j < n) { if(nums1[i] < nums2[j]) { nums[count++] = nums1[i]; i++; } else if (nums1[i] > nums2[j]) { nums[count++] = nums2[j]; j++; } else { nums[count++] = nums1[i]; nums[count++] = nums2[j]; i++; j++; } } if(i <= m-1) { for(int k = i; k < m; k++) { nums[count++] = nums1[k]; } } if(j <= n-1) { for(int k = j; k < n; k++) { nums[count++] = nums2[k]; } } double result; if(nums.length % 2 == 0) { result = (nums[nums.length/2] + nums[nums.length/2-1]) / 2.0; } else { result = nums[nums.length / 2]; } return result; }
-
思路2:借助二分查找
根据时间复杂度要求
O(log(m+n))
,其实就是在暗示需要用二分查找。- 思路如下:
-
因为两个数组都是正序的,因此通过分别分割两个数组来寻找中位数,中位数肯定是两个数组的分割线左右的四个数中;
-
分割线需要满足的条件:
①左侧元素的个数 = 右侧元素的个数( + 1);默认如果 m + n是奇数,分割的时候让左边多一个数
②左侧元素的数值 <= 右侧元素的数值
-
只需要盯着一个数组来考虑分割线位置,技巧,盯着较短的数组来找分割线位置,即只要找到nums1分割后左侧元素的个数,用 (m + n + 1) /2 减掉就得到nums2分割后左侧元素的个数;
-
此时,就可以得到分割线左右四个元素,这里注意要判断分割线的条件②,即交叉比较是否 nums1Left <=nums2Right && nums2Left <= nums1Right;
-
如果不满足 nums1Left <=nums2Right,说明分割线太右边了,需要考虑左边那一半;
-
如果不满足nums2Left <= nums1Right,说明分割线太左边了,需要考虑右边那一半;
-
注意要处理边界问题。
-
public static double findMedianSortedArraysS2(int[] nums1, int[] nums2) { if(nums1.length > nums2.length) { int[] temp = nums1; nums1 = nums2; nums2 = temp; } int m = nums1.length; int n = nums2.length; //如果有一个数组为空 那肯定是nums1 if(m == 0) { if((m + n) % 2 == 1) { return nums2[n/2]; } else { return (nums2[n/2] + nums2[n/2-1]) / 2.0; } } int left = 0; int right = m; while (left <= right) { int i = (left + right + 1) / 2; int j = (m + n + 1) / 2 - i; if(j != 0 && i != m && nums2[j-1] > nums1[i]) { left = i + 1; }else if(i != 0 && j !=n && nums1[i-1] > nums2[j]) { right = i - 1; } else { int maxLeft; if(i == 0) { maxLeft = nums2[j-1]; }else if(j == 0) { maxLeft = nums1[i-1]; }else { maxLeft = Math.max(nums1[i-1], nums2[j-1]); } int minRight; if(i == m) { minRight = nums2[j]; }else if(j == n) { minRight = nums1[i]; }else { minRight = Math.min(nums1[i], nums2[j]); } if((m+n) % 2 == 1) { return maxLeft; } else { return (maxLeft + minRight) / 2.0; } } } return 0.0; }
- 思路如下:
-
思路3:借助第K小数的思想
利用第k小数的思想+递归二分法
-
思路如下:
① 还是数组总长度奇偶数的情况,注意找的是第K个小数,K是比数组索引多了1的。
a.奇数,找第
(m+n)/2 + 1
,即k = (m+n)/2 + 1
; b.偶数,找第
(m+n)/2
和第(m+n)/2 + 1
,即要找两次k;②有两种情况:比较nums1[k/2-1]和nums2[k/2-1]
a. nums1[k/2-1] <= nums2[k/2-1]:说明比nums1[k/2-1]小的数最多只有nums1中的前k/2-1个数和nums2中前k/2-1个数,即比nums1[k/2-1]小的最多只有k-2个数,因此可以排除[nums1[0],nums1[k/2-1]];
b.nums1[k/2-1] > nums2[k/2-1]:可以排除[nums2[0],nums2[k/2-1]];
③有三种特殊情况需要处理:
a.有一个数组为空;
b.k==1;
c.数组越界。
-
public static double findMedianSortedArraysS4(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
if((m + n) % 2 == 1) {
return findKthMinElement(nums1, nums2, (m+n)/2 + 1);
}else {
return (findKthMinElement(nums1,nums2,(m+n)/2) + findKthMinElement(nums1,nums2,(m+n)/2+1)) / 2.0;
}
}
/**
* 获取两个正序数组中第k小数
* @param nums1
* @param nums2
* @param k
* @return
*/
public static int findKthMinElement(int[] nums1, int[] nums2, int k) {
int m = nums1.length;
int n = nums2.length;
int i = 0;
int j = 0;
while (true) {
//特殊情况1:有一个数组为空
if(i == m) {
return nums2[j + k -1];
}
if(j == n) {
return nums1[i + k - 1];
}
//特殊情况2:k=1
if(k == 1) {
return Math.min(nums1[i], nums2[j]);
}
//正常情况开始
int half = k/2;
//特殊情况2:处理数组越界
int newI = Math.min(i+half-1,m-1);
int newJ = Math.min(j+half-1,n-1);
if(nums1[newI] <= nums2[newJ]) {
k -= newI - i + 1;
i = newI + 1;
} else {
k -= newJ - j + 1;
j = newJ + 1;
}
}
}