4. 寻找两个正序数组的中位数
4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
class Solution {
int KthSmallest(vector<int> &V1, vector<int> &V2, int K) {
int Size1 = V1.size(), Size2 = V2.size();
int I = 0, J = 0;
while (true) {
// To get Kth smallest means that K is ORDER, not INDEX, so in fact 1 is
// the identity here. And if we want to use K to INDEX, we need to convert
// it to 0-indexed, i.e. minus by one.
if (I == Size1)
return V2[J + (K - 1)];
if (J == Size2)
return V1[I + (K - 1)];
if (K == 1)
return std::min(V1[I], V2[J]);
int Half = K / 2;
// Comparison happens INSIDE the to-be-eliminated part.
int NewI = std::min(I + Half, Size1) - 1;
int NewJ = std::min(J + Half, Size2) - 1;
if (V1[NewI] < V2[NewJ]) {
// Eliminate elements at the front. Keep in mind that comparison happens
// INSIDE, so we need to add one here to convert INDEX to SIZE.
K -= NewI - I + 1;
I = NewI + 1;
} else {
K -= NewJ - J + 1;
J = NewJ + 1;
}
}
}
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int Total = nums1.size() + nums2.size();
if (Total % 2 == 0)
return (KthSmallest(nums1, nums2, Total / 2) +
KthSmallest(nums1, nums2, Total / 2 + 1)) / 2.;
else
return KthSmallest(nums1, nums2, Total / 2 + 1);
}
};
这个题最关键的点在于两个部分。第一,是把「求中位数」转化为「求第 K 小的数,其中 K 为中位数的 1-indexed 序数」;第二,是在待排序列内部进行的比较策略。这两个策略都涉及到大量基数、1-indexed 序数和 0-indexed 序数的转换。
注意官解对于 I 和 J 的步进是「懒汉式」的,也即最开始大家都是 0,直到 NewI 和 NewJ 被确定之后再开始步进。实际上这道题确实需要记录新旧两种 I,但是记录 OldI 我试过,很混乱,而且上来就要设定 I 和 J 不优雅,所以还是官解这种 NewI 比较好。
注意设置 NewI 的时候那个 std::min() 很关键,没有它的话边界条件就不是严格等于而是大于等于了,说实话我感觉不设这个可能都不太能跑起来。
二刷
class Solution {
int KthSmallest(vector<int> &A, vector<int> &B, int K) {
int SizeA = A.size(), SizeB = B.size();
// i and j are 0-indexed; K is 1-indexed.
int i = 0, j = 0;
while (true) {
// Check for corner cases.
if (i == SizeA)
return B[j + (K - 1)];
if (j == SizeB)
return A[i + (K - 1)];
// Check if we have counted to the last number.
if (K == 1)
return std::min(A[i], B[j]);
// Make Next pointers no larger than end().
int NextI = std::min(SizeA, i + K / 2);
int NextJ = std::min(SizeB, j + K / 2);
// Compare INCLUSIVE ranges, and update K correspondingly.
if (A[NextI - 1] < B[NextJ - 1]) {
K -= NextI - i;
i = NextI;
} else {
K -= NextJ - j;
j = NextJ;
}
}
}
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int TotalSize = nums1.size() + nums2.size();
if (TotalSize % 2 == 0)
return (KthSmallest(nums1, nums2, TotalSize / 2) +
KthSmallest(nums1, nums2, TotalSize / 2 + 1)) / 2.;
else
return KthSmallest(nums1, nums2, TotalSize / 2 + 1);
}
};
这次是用左闭右开的语义写的,i 和 j 仍然是 0-indexed,但是关于 NextI 和 NextJ 的处理就清爽了很多,「在待排序列内部比较」的策略也明确了,就是那个在下标当中 -1 的操作。
但是说实话,这个 KthSmallest() 的写法还是有点环环相扣的感觉。你可以看到,我们既然把不动点设置在开头、只考察 i 和 j 而非 Next,并且条件写得非常严格(要求严格等于 Size 而不是大于等于),那么就必须要在循环的最后、下一次循环开始之前把 Next 指针的范围控制住,以及相对应地更新 K 的值。
说实话,这样写应该确实是最好的。不动点的判定本就应该尽量简单。这道题可以理解,而且也应该背下来。