1、题目描述:
困难题
2、题解:
方法1:暴力法
思路:
1、使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。
2、不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是
已知的。维护两个指针,初始时分别指向两个数组的下标 0 的位置,每次将指向较小值的指针后移一位(如果一个指针已经
到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。
合并两个有序的数组后找中位数
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
#暴力法,合并两个有序的数组
res = []
m,n = len(nums1),len(nums2)
if m == 0:
if n % 2 == 0:
return (nums2[n//2] + nums2[n // 2 - 1]) / 2
else :
return nums2[n // 2]
if n == 0:
if m % 2 == 0:
return (nums1[m // 2] + nums1[m // 2 - 1]) / 2
else:
return nums1[m // 2]
i ,j = 0,0
while i < m and j < n:
if nums1[i] < nums2[j]:
res.append(nums1[i])
i += 1
else:
res.append(nums2[j])
j += 1
res += nums1[i:] if i < m else nums2[j:]
size = len(res)
if size % 2 == 0:
return (res[size // 2] + res[size // 2 - 1]) / 2
else:
return res[size // 2]
方法2:一次扫描
方法1的空间复杂为O(M+N),我们可以优化空间复杂度,思路:
先不考虑长度是奇数还是偶数,先创建一个方法,在两个有序数组中,求第k个小的数。
最后再考虑奇数、偶数的问题
python代码如下:
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# 遍历,找第k小的数
def getKthnum(nums1, nums2, k):
if not nums1 and not nums2: return -1
if not nums1: return nums2[k - 1]
if not nums2: return nums1[k - 1]
index1, index2 = 0, 0
while True:
# 特殊情况
if index1 == m:
return nums2[index2 + k - 1]
if index2 == n:
return nums1[index1 + k - 1]
if k == 1:
return min(nums1[index1], nums2[index2])
# 一般情况
if index1 < m and index2 < n and nums1[index1] <= nums2[index2]:
index1 += 1
k -= 1
elif index1 < m and index2 < n and nums1[index1] > nums2[index2]:
index2 += 1
k -= 1
m, n = len(nums1), len(nums2)
totalLen = m + n
if totalLen % 2 == 1:
return getKthnum(nums1, nums2, totalLen // 2 + 1)
else:
return (getKthnum(nums1, nums2, totalLen // 2) + getKthnum(nums1, nums2, totalLen // 2 + 1)) / 2
或者,用另外一种写法,
思路:
我们找totlaLen // 2 + 1个数,然后每次找的时候都记录上次得到的数(left = right)
最后处理奇数和偶数的问题。
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m,n = len(nums1),len(nums2)
totalLen = m + n
left,right = -1,-1
index1,index2 = 0,0
for _ in range(totalLen//2 + 1):
left = right
if index1 < m and (index2 >= n or nums1[index1] <= nums2[index2]):
right = nums1[index1]
index1 += 1
else:
right = nums2[index2]
index2 += 1
if totalLen % 2 == 1:
return right
else:
return (left + right) / 2
方法3:二分查找
思路:
中位数的定义:当m + n为奇数时,中位数是两个有序数组的第(m+n)/2个元素,当m+n为偶数时,中位数是(m+n)/2、(m+n)/2+1
个元素的平均值。所以可以转化成寻找两个有序数组中的第k小的数,k为(m+n)/2或(m+n)/2+1。
分别求出nums1,nums2的第 k / 2个数 a 和 b.
然后比较 a 和 b:
当a < b ,说明第 k 个数位于 a数组的第 k / 2个数后半段,或者b数组的 第 k / 2 个数前半段,
问题规模缩小了一半,然后递归处理就行。
- 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
- 这里的 "/" 表示整除
- nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
- nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
- 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
- 这样 pivot 本身最大也只能是第 k-1 小的元素
- 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
- 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
- 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
Python代码如下:
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
#二分查找
def getKthElement(k):
index1,index2 = 0,0
while True:
#特殊情况
if index1 == m:
return nums2[index2 + k -1]
if index2 == n:
return nums1[index1 + k - 1]
if k == 1:
return min(nums1[index1],nums2[index2])
#一般情况
newIndex1 = min(index1 + k // 2 - 1,m - 1)
newIndex2 = min(index2 + k // 2 - 1 , n - 1)
pivot1,pivot2 = nums1[newIndex1],nums2[newIndex2]
if pivot1 <= pivot2:
k -= newIndex1 - index1 + 1
index1 = newIndex1 + 1
else:
k -= newIndex2 - index2 + 1
index2 = newIndex2 + 1
m,n = len(nums1),len(nums2)
totalLen = m + n
if totalLen % 2 == 1:
return getKthElement((totalLen + 1) // 2)
else:
return ((getKthElement(totalLen // 2)) + getKthElement(totalLen // 2 + 1)) / 2
C++实现:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
//二分查找
int col = nums1.size(),row = nums2.size();
int total = col + row;
if (total % 2 == 1){
return getKthElement(nums1,nums2,total / 2 + 1);
}
else{
return (getKthElement(nums1,nums2,total / 2) + getKthElement(nums1,nums2,total / 2 + 1)) / 2.0;
}
}
int getKthElement(vector<int>& nums1,vector<int>& nums2,int k){
int col = nums1.size(),row = nums2.size();
int index1 = 0,index2 = 0;
while (true){
if (index1 == col){
return nums2[index2 + k - 1];
}
if (index2 == row){
return nums1[index1 + k - 1];
}
if (k == 1){
return min(nums1[index1],nums2[index2]);
}
int newindex1 = min(index1 + k / 2 - 1,col - 1);
int newindex2 = min(index2 + k / 2 - 1,row - 1);
if (nums1[newindex1] <= nums2[newindex2]){
k -= newindex1 - index1 + 1;
index1 = newindex1 + 1;
}
else{
k -= newindex2 - index2 + 1;
index2 = newindex2 + 1;
}
}
}
};
方法4:数组划分
分割: 割会分有序数组为两部分,割左边必定小于右边,割的左右两边会有两个元素 lmax,rmin。当数组长度为
偶数时,割在两数中间;当数组长度为奇数时,割在1个数上。
可以把两个数组合成一个数组,分奇数和偶数进行分别计算。当然也可以考虑加'#'号,当然这里是虚拟加的'#'
[1,2,3] -> [#,1,#,2,#,3,#]
[5,6] -> [#,5,#,6,#]
这样处理,两个数组的长度和必定是偶数,每个位置可以通过/2得到原来元素的位置,分割点这样对于numi来说,必定有以下两个部分之一成立:
lmaxi = (ci - 1) // 2位置上的元素
rmaxi = ci // 2 位置上的元素
二分的依据:
m,n = len(nums1),len(nums2)
left,right = 0,len(nums1) * 2
当lmax1 > rmin2时:
说明数组1的左边元素太多,减小c1,那么c2 = m+n-c1就相应增大;c1向左二分,就让 right = c1-1
如果 lmax2 > rmin1时:
说明数组2左边的元素太多,减小c2,那么c1= m+n - c2,就相应增大;c1向右二分,就让left = c1+1
最终返回 (max(lmax1,rmax1) + min(rmin1,rmin2)) / 2
当然也要处理以下4中情况:
lmax1 = float('-inf') if c1 == 0 else nums1[(c1 - 1) // 2],割在数组1开头,割的左边是空,可以认为lmax1 = float('-inf')
rmin1 = float('inf') if c1 == 2 * m else nums1[c1 // 2],割在数组1的结尾,割的右边是空,可以认为rmin1 = float('inf')
lmax2 = float('-inf') if c2 == 0 else nums2[(c2 - 1) // 2],割在数组2的开头,割的左边是空,可以认为lmax2 = float('-inf')
rmin2 = float('inf') if c2 == 2 * n else nums2[c2 // 2],割在数组2的开头,割的左边是空,可以认为rmin2 = float('inf')
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
#划分数组
m,n = len(nums1),len(nums2)
if m > n: #保证数组1一定最短
return self.findMedianSortedArrays(nums2,nums1)
#Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
left ,right = 0,2*m #虚拟加了'#'所以数组1是2*n长度
while left <= right:
c1 = left + (right - left) // 2 #c1是二分的结果
c2 = m + n - c1
lmax1 = float('-inf') if c1 == 0 else nums1[ (c1 - 1) // 2]
rmin1 = float('inf') if c1 == 2*m else nums1[c1 // 2]
lmax2 = float('-inf') if c2 == 0 else nums2[(c2 - 1) // 2]
rmin2 = float('inf') if c2 == 2*n else nums2[c2 // 2]
if lmax1 > rmin2: #c1向左二分
right = c1 - 1
elif lmax2 > rmin1: #c1向右二分
left = c1 + 1
else:
break
return (max(lmax1,lmax2),min(rmin1,rmin2)) / 2
C++实现:
//在自己的编译器中,加头文件很重要
#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
if (n > m) //保证数组1一定最短
{
return findMedianSortedArrays(nums2, nums1);
}
// Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n; //我们目前是虚拟加了'#'所以数组1是2*n长度
while (lo <= hi) //二分
{
c1 = (lo + hi) / 2; //c1是二分的结果
c2 = m + n - c1;
LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];
if (LMax1 > RMin2)
hi = c1 - 1;
else if (LMax2 > RMin1)
lo = c1 + 1;
else
break;
}
return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
}
};
int main(int argc, char *argv[])
{
vector<int> nums1 = { 2,3, 5 };
vector<int> nums2 = { 1,4,7, 9 };
Solution solution;
double res = solution.findMedianSortedArrays(nums1, nums2);
cout << res <<endl;
return 0;
}
3、复杂度分析:
方法1:
时间复杂度:O(M+N),M、N分别为两个数组的长度
空间复杂度:O(M+N)
方法2:
时间复杂度:O(M+N),M、N分别为两个数组的长度
空间复杂度:O(1)
方法3:
时间复杂度:O(log(M+N)),M、N分别为两个数组的长度
空间复杂度:O(1)
方法4:
时间复杂度:O(log(min(M,N)))
空间复杂度:O(1)