welcome to my blog
LeetCode Top 100 Liked Questions 4.Median of Two Sorted Arrays(Java版; Hard)
题目描述
There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
You may assume nums1 and nums2 cannot be both empty.
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
(1)在两个排序数组中找到第k小的数
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length, m = nums2.length;
int count = n + m;
if((count&1)==1){
return core(nums1, nums2, 0, 0, count/2+1);
}else{
return (core(nums1, nums2, 0, 0, count/2) + core(nums1, nums2, 0, 0, count/2+1))/2.0;
}
}
private double core(int[] nums1, int[] nums2, int i, int j, int k){
//base case
int n = nums1.length, m = nums2.length;
if(i>=n){
return nums2[j+k-1];
}else if(j>=m){
return nums1[i+k-1];
}
if(k==1){
return Math.min(nums1[i], nums2[j]);
}
//第k/2个数的索引
int mid1 = i + k/2 - 1;
int mid2 = j + k/2 - 1;
int a = mid1 >= n ? Integer.MAX_VALUE : nums1[mid1];
int b = mid2 >= m ? Integer.MAX_VALUE : nums2[mid2];
if(a<b){
return core(nums1, nums2, mid1+1, j, k-k/2);
}else{
return core(nums1, nums2, i, mid2+1, k-k/2);
}
}
}
第一次做,看了题解才会的, 对切割位置进行二分; m个元素有m+1个切割位置, 切割位置取值为0,1,2,…,m, 当切割位置为i时表示左边有i个元素, 同时i作为索引时指向切割后右边的第一个元素; 是对短数组的切割位置进行二分, 不是长数组; 规定, 元素总数为偶数时, 左一左二的元素数量等于右一右二; 元素总数为奇数时, 左一左二的元素数量比右一右二多一个; while循环中的越界检查也非常重要
设arr1长度为m, arr2长度为n, m+n是奇数
将arr1分成两部分, 将arr2分成两部分, arr1的两部分对应左一和右一, arr2的两部分对应左二和右二
如果左一<=右二, 同时左二<=右一, 那么左一左二的元素都小于右一右二的元素,此时如果左一左二的元素个数相加等于(m+n+1)/2, 由于m+n是奇数, 所以中间的元素是第(m+n+1)/2个, 那么中位数就等于左一左二中的最大值
如果左一的最大值大于右二的最小值, 说明左一中的元素过多, 需要减少(分割线左移:这个操作涉及二分), 与此同时, 由于左一和左二的元素个数和为(m+n+1)/2, 所以左二中的元素个数增加
如果左二的最大值大于右一的最小值, 说明左二中的元素过多, 需要减少(分割线左移:这个操作涉及二分), 与此同时, 由于左一和左二的元素个数和为(m+n+1)/2, 所以左一中的元素个数增加
如果左一是整个arr1时, 此时没有右一, 中位数是左一左二的最大值
如果右一是整个arr1时, 此时没有左一, 中位数是左二的最大值
如果左二是整个arr2时, 此时没有右二, 中位数左一左二的最大值
如果右二是整个arr2时, 此时没有左二, 中位数是左一的最大值
=======================================================================================================
=======================================================================================================
设arr1长度为m, arr2长度为n, m+n是偶数
将arr1分成两部分, 将arr2分成两部分, arr1的两部分对应左一和右一, arr2的两部分对应左二和右二
如果左一<=右二, 同时左二<=右一, 那么左一左二的元素都小于右一右二的元素, 此时如果左一左二的元素个数相加等于(m+n)/2, 由于m+n是偶数, 所以中间的元素有两个, 分别是第(m+n)/2个和第(m+n)/2+1个, 分别对应左一左二中的最大值和右一右二中的最小值
如果左一的最大值大于右二的最小值, 说明左一中的元素过多, 需要减少(分割线左移:这个过程涉及二分), 与此同时, 由于左一和左二的元素个数和为(m+n)/2, 所以左二中的元素个数增加
如果左二的最大值大于右一的最小值, 说明左二中的元素过多, 需要减少(分割线左移:这个过程涉及二分), 与此同时, 由于左一和左二的元素个数和为(m+n)/2, 所以左一中的元素个数增加
如果左一是整个arr1时, 此时没有右一, 中位数是左一左二最大值和右二最小值的平均值
如果右一是整个arr1时, 此时没有左一, 中位数是左二最大值和右一右二最小值的平均值
如果左二是整个arr2时, 此时没有右二, 中位数是左一左二最大值和右二最小值的平均值
如果右二是整个arr2时, 此时没有右一, 中位数是左一最大值和左二右二最小值的平均值
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if(nums1==null)
return nums2.length%2==1 ? nums2[nums2.length/2]*1.0 : (nums2[nums2.length/2-1]+nums2[nums2.length/2])/2.0;
if(nums2==null)
return nums1.length%2==1 ? nums1[nums1.length/2]*1.0 : (nums1[nums1.length/2-1] + nums1[nums1.length/2])/2.0;
int m = nums1.length, n = nums2.length;
//让nums1作为短的数组, 后面对短的数组进行二分
if(m>n){
int[] temp = nums1; nums1 = nums2; nums2 = temp;
int tmp = m; m = n; n = tmp;
}
//右一最小值索引
int i;
//右二最小值索引
int j;
//left和right表示切割的位置, 0表示切割后左边有0个元素, 1表示切割后左边有1个元素
int left = 0, right = m;
//循环条件
while(left <= right){
//对短数组的切割边界进行二分
i = (left + right )/2; //i作为切割线表示左边有i个元素, 作为索引指向切割后右边第一个元素
//令j也指向右边第一个, 那么左一左二的元素个数为i+j满足 i+j = (m+n)/2
j = (m+n+1)/2 - i; //m+n是偶数时, 左一左二的元素个数和右一右二相等; m+n是奇数时, 左一左二比右一右二多一个
//左一大于右二, 将nums1分割线右边界左移,缩小左一范围
//要保证左一有元素, 所以i>left
if(i > left && nums1[i-1] > nums2[j])
right = i - 1;
//左二大于右一, 将nums1分割线左边界右移,扩大左一范围, 从而缩小左二范围
//要保证右一有元素, 所以i<right
else if(i < right && nums2[j-1] > nums1[i])
left = i + 1;
//左一<=右二 && 左二<=右一
else{
int leftMax;
//右一是整个nums1
if(i==0)
leftMax = nums2[j-1];
//右二是整个nums2
else if(j==0)
leftMax = nums1[i-1];
else
leftMax = Math.max(nums1[i-1], nums2[j-1]);
if((m+n)%2==1)
return leftMax;
int rightMin;
//左一是整个nums1
if(i==m)
rightMin = nums2[j];
//左二是整个nums2
else if(j==n)
rightMin = nums1[i];
else
rightMin = Math.min(nums1[i], nums2[j]);
return (leftMax+rightMin)/2.0;
}
}
return 0.0;
}
}
第一次做, 模拟归并过程, 但并不创建辅助数组, 只需要记录两个位置的元素, 一个是len/2-1, 另一个是len/2; while循环到len/2-1即可, 我最开始循环到len/2处, 这样每次循环需要保存上一次循环的数, 容易写错. 循环到len/2-1处,之后再单独寻找len/2处的元素就方便多了; 不过不满足时间复杂度O(logm+n)的要求, 得用二分法
/*
将两个有序数组归并到中点处, 模拟归并过程, 不需要真正的进行归并
*/
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if(nums1==null)
return nums2.length%2==1 ? nums2[nums2.length/2]*1.0 : (nums2[nums2.length/2-1]+nums2[nums2.length/2])/2.0;
if(nums2==null)
return nums1.length%2==1 ? nums1[nums1.length/2]*1.0 : (nums1[nums1.length/2-1]+nums1[nums1.length/2])/2.0;
int len = nums1.length + nums2.length;
int p1 =0, p2=0, i=0;
int curr = 0;
//循环到len/2-1处
while(p1<nums1.length && p2<nums2.length && i<len/2){
if(nums1[p1] <= nums2[p2]){
curr = nums1[p1];
p1++;
}
else{
curr = nums2[p2];
p2++;
}
i++;
}
while(p1<nums1.length && i<len/2){
curr = nums1[p1];
p1++;
i++;
}
while(p2<nums2.length && i<len/2){
curr = nums2[p2];
p2++;
i++;
}
int next=0;
if(p1==nums1.length)
next = nums2[p2];
else if(p2==nums2.length)
next = nums1[p1];
else
next = Math.min(nums1[p1], nums2[p2]);
return len%2==1 ? next*1.0 : (curr + next)/2.0;
}
}
题解
class Solution {
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
if (m > n) { // to ensure m<=n
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - i;
if (i < iMax && B[j-1] > A[i]){
iMin = i + 1; // i is too small
}
else if (i > iMin && A[i-1] > B[j]) {
iMax = i - 1; // i is too big
}
else { // i is perfect
int maxLeft = 0;
if (i == 0) { maxLeft = B[j-1]; }
else if (j == 0) { maxLeft = A[i-1]; }
else { maxLeft = Math.max(A[i-1], B[j-1]); }
if ( (m + n) % 2 == 1 ) { return maxLeft; }
int minRight = 0;
if (i == m) { minRight = B[j]; }
else if (j == n) { minRight = A[i]; }
else { minRight = Math.min(B[j], A[i]); }
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
}