给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
一、合并方法
最简单的方法就是将两个数组合并到另一个数组当中,再在这个数组中找到中位数即可。使用for循环将两个数组合并的方法考虑的情况较复杂 ,可以直接使用while循环将其中一个数组合并,那么,接着再使用一个for循环将另一个数组中剩余的量存入即可。该方法空间复杂度为O(m+n),时间复杂度也是一样O(m+n)。
代码:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
const int m = nums1.size(), n = nums2.size(), half = (n + m) >> 1;
int n1 = 0, n2 = 0;
vector<int> res(n + m);
for (int k = 0, i = 0, j = 0; k < res.size(); ++k)
{
if (i < m && j < n)
{
n1 = nums1[i];
n2 = nums2[j];
if (n1 > n2)
{
res[k] = n2;
if (j < n)
j++;
}
else if (n1 <= n2)
{
res[k] = n1;
if (i < m)
i++;
}
continue;
}
if (n == j)
{
res[k] = nums1[i];
i++;
}
else if (m == i)
{
res[k] = nums2[j];
j++;
}
}
if ((n + m) % 2 == 0)
{
return (res[half - 1] + res[half]) / 2.0f;
}
return res[half];
}
代码优化:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
const int m = nums1.size(), n = nums2.size(), half = (n + m) >> 1;
int i = 0, j = 0, k = 0;
vector<int> res(n + m);
while (i < m && j < n)
{
res[k++] = nums1[i] > nums2[j] ? nums2[j++] : nums1[i++];
}
while (i < m)
{
res[k++] = nums1[i++];
}
while (j < n)
{
res[k++] = nums2[j++];
}
if ((n + m) % 2 == 0)
{
return (res[half - 1] + res[half]) / 2.0f;
}
return res[half];
}
二、双指针法
使用两个"指针"分别指向两个数组,比较指向的值的大小,值小的指针向后移动一位,如此循环。何时停止这个问题至关重要,我们需要得到中位数,那么移动的次数只要和中位数所在的位置一致即可。当然,中位数还有数组长度是偶数还是奇数的情况。当是奇数时,那么中位数就是中间位置的数,那么循环len/2次就是中位数;若是偶数,那么需要记录前一个值,所以整个空间复杂度为O(1)。
代码:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
const int n1 = nums1.size(), n2 = nums2.size(), len = n1 + n2;
int lastVal = 0, curVal = 0;
int p1 = 0, p2 = 0;
for (int k = 0; k <= len / 2; k++)
{
lastVal = curVal;
if (p1 < n1 && p2 < n2)
{
if (nums1[p1] < nums2[p2])
{
curVal = nums1[p1];
p1++;
}
else
{
curVal = nums2[p2];
p2++;
}
continue;
}
if (p1 == n1 && p2 < n2)
{
curVal = nums2[p2];
p2++;
}
if (p2 == n2 && p1 < n1)
{
curVal = nums1[p1];
p1++;
}
}
return len % 2 == 0 ? (curVal + lastVal) / 2.0f : curVal;
}
代码优化,这里直接将p2 == n2 && p1 < n1的情况和p1 < n1 && p2 < n2进行合并,同时将其与nums1[p1] < nums2[p2]"或"在一起,这样即p2>=n2也不会执行后面大小比较导致越界了:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
const int n1 = nums1.size(), n2 = nums2.size(), len = n1 + n2;
int lastVal = 0, curVal = 0;
int p1 = 0, p2 = 0;
for (int k = 0; k <= len / 2; k++)
{
lastVal = curVal;
if (p1 < n1 && (p2 >= n2 || nums1[p1] < nums2[p2]))
{
curVal = nums1[p1];
p1++;
}
else
{
curVal = nums2[p2];
p2++;
}
}
return len % 2 == 0 ? (curVal + lastVal) / 2.0f : curVal;
}
三、二分法
由于是有序的两个数组那么自然就可以使用二分法进行解决。每次两个数组中一个位移K/2个位置直至抵达返回条件。
递归方法代码:
int getKthElement(int k, int s1, int s2, const vector<int>& nums1, const vector<int>& nums2)
{
const int n1 = nums1.size(), n2 = nums2.size();
if (s1 >= n1)
{
return nums2[s2 + k - 1];
}
else if (s2 >= n2)
{
return nums1[s1 + k - 1];
}
if (k == 1)
{
return std::min(nums1[s1], nums2[s2]);
}
int preLen1 = std::min(k / 2, n1 - s1);
int preLen2 = std::min(k / 2, n2 - s2);
if (nums1[s1 + preLen1 - 1] < nums2[s2 + preLen2 - 1])
{
return getKthElement(k - preLen1, s1 + preLen1, s2, nums1, nums2);
}
else
{
return getKthElement(k - preLen2, s1, s2 + preLen2, nums1, nums2);
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
const int len = nums1.size() + nums2.size();
if (len % 2 == 1)
{
return getKthElement((len + 1) / 2, 0, 0, nums1, nums2);
}
else
{
return (getKthElement(len / 2, 0, 0, nums1, nums2) + getKthElement(len / 2 + 1, 0, 0, nums1, nums2)) / 2.0f;
}
}
使用while循环代码(建议使用该方法):
int getKthElement(int k, const vector<int>& nums1, const vector<int>& nums2)
{
const int len1 = nums1.size(), len2 = nums2.size();
int i = 0, j = 0;
while(true) {
if(i == len1) { // 边界条件是需要我们时时刻刻都需要注意的
return nums2[j + k - 1];
}
if(j == len2) {
return nums1[i + k - 1];
}
// k==1不在while()中的原因是当i==len1或者j==len2时会直接超出数组范围
if(k == 1) {
break;
}
// 防止每次向后移位的索引超出数组的范围
int idx1 = std::min(i + k/2 - 1, len1 - 1);
int idx2 = std::min(j + k/2 - 1, len2 - 1);
if(nums1[idx1] < nums2[idx2]) { // 如果该条件成立说明idx1代表的k/2个数是可以丢弃的,因为第K个数不在其中
k -= idx1 - i + 1; // k在前的原因是在i+=k/2前将k-=k/2,防止多减
i = idx1 + 1; // 通常情况下是i+=k/2,当索引到达最后一位时idx1+1就恒为数组长度
}
else {
k -= idx2 - j + 1;
j = idx2 + 1;
}
}
return std::min(nums1[i], nums2[j]);
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
const int len1 = nums1.size(), len2 = nums2.size();
int k = (len1 + len2) / 2 + 1;
return (len1 + len2) % 2 == 0 ? (getKthElement(k, nums1, nums2) + getKthElement(k - 1, nums1, nums2)) / 2.f : getKthElement(k, nums1, nums2);
// 或者采取以下方法,虽然会使得函数看起来简单,但实际上增加了计算次数
// int k1 = (len1 + len2 + 1) / 2, k2 = (len1 + len2 + 2) / 2;
// return (getKthElement(k1, nums1, nums2) + getKthElement(k2, nums1, nums2)) / 2.f;
}
进行优化(需要多加理解):
// https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/jiang-qi-zhuan-wei-zhao-liang-ge-you-xu-shu-zu-de-/
// https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
int getKthElement(int k, const vector<int>& nums1, const vector<int>& nums2)
{
const int len1 = nums1.size(), len2 = nums2.size();
// 找到两个数组第K个小的数,需要满足以下两个条件,注意这里的i和j表示的是第i个数和第j个数:
// 1、i + j = k
// 2、nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
// 对于条件2,因为原本nums1[i-1]就小于nums1[i],所以这里就没有加上,
// 而且两个数组的左边总共有k个数小于右边,这就意味着第k个小的数就是
// max(nums1[i-1], nums2[j-1])。那么以此为条件可以得到i的范围,注意i=0表示数组为空:
// 1、0 <= i <= len1
// 2、0 <= j <= len2 && i + j = k ==> k - len2 <= i <= k
// 综合起来就是max(0, k - len2) <= i <= min(len1, k)
int l = std::max(0, k - len2), r = std::min(len1, k);
while(l < r) {
int m = (l + r) / 2;
// 只要条件2中有一个不等式不成立,就意味着这不是第K小数,继续移动
// 左边的指针,注意这里满足该不等式的肯定不满足条件,因此直接+1移动,
// 直到满足nums2[k-m-1] <= nums1[m]。当满足该条件下,持续左移右边
// 的指针,注意这里的m是可能为正确值的,因此不能+1而是直接赋值
if(nums1[m] < nums2[k-m-1]) {
l = m + 1;
}
else {
r = m;
}
// 为什么条件2中的只要其中一条满足即可?因为当i递增时,
// nums1[i-1]递增,nums2[j]递减,一定存在一个最大的值i满足:
// nums1[i-1] <= nums2[j],则此时i+1必定不满足条件,代入即
// nums1[i+1-1] > nums2[j-1] ==> nums1[i] > nums2[j-1],这个
// 其实就是条件2的第二个不等式,所以二者是等同的
}
// 跳出while时l==r,由于是按照nums2[j-1] <= nums1[i]该条件进行查找的,
// 那么此时i的值为l,表示第k小数为nums1[l-1]和nums2[k-l-1]这两个数的其
// 中一个,取二者其最大值,注意这里的l的范围是0~k,因此:
int cond1 = 0 == l? INT_MIN : nums1[l-1];
int cond2 = k == l? INT_MIN : nums2[k-l-1];
return std::max(cond1, cond2);
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
const int len1 = nums1.size(), len2 = nums2.size();
int k = (len1 + len2) / 2 + 1;
return (len1 + len2) % 2 == 0 ? (getKthElement(k, nums1, nums2) + getKthElement(k - 1, nums1, nums2)) / 2.f : getKthElement(k, nums1, nums2);
// 或者采取以下方法,虽然会使得函数看起来简单,但实际上增加了计算次数
// int k1 = (len1 + len2 + 1) / 2, k2 = (len1 + len2 + 2) / 2;
// return (getKthElement(k1, nums1, nums2) + getKthElement(k2, nums1, nums2)) / 2.f;
}
四、分割法
每次在两个数组上切一刀,分别比较这刀左右两边大小。若左边大于右边说明还未抵达正确位置,左边的位置++,直至找到左边小于右边的位置即是中位数的位置。
代码:
double getMedianValue(vector<int>& nums1, vector<int>& nums2)
{
const int n1 = nums1.size(), n2 = nums2.size();
int left = 0, right = n1, mid1 = 0, mid2 = 0;
while (left <= right)
{
int i = (left + right) / 2;
int j = (n1 + n2 + 1) / 2 - i;
int lmaxi = i == 0 ? INT_MIN : nums1[i - 1];
int rmaxi = i == n1 ? INT_MAX : nums1[i];
int lmaxj = j == 0 ? INT_MIN : nums2[j - 1];
int rmaxj = j == n2 ? INT_MAX : nums2[j];
if (lmaxj > rmaxi)
{
left = i + 1;
}
else if (lmaxi > rmaxj)
{
right = i - 1;
}
else
{
mid1 = std::max(lmaxi, lmaxj);
mid2 = std::min(rmaxi, rmaxj);
break;
}
}
return (n1 + n2) % 2 == 0 ? (mid1 + mid2) / 2.0f : mid1;
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
return nums1.size() > nums2.size() ? getMedianValue(nums2, nums1) : getMedianValue(nums1, nums2);
}