LeetCode题目:4.Median of Two Sorted Arrays
原题链接:https://leetcode.com/problems/median-of-two-sorted-arrays/description/
解题思路:
由于时间复杂度限制在了 O(log (m+n)),因此不能遍历整个数组。由于是log,因此比较容易想到的是关于二分的算法。
在一个排序好的数组中,查找一个特定值所需的时间为O(log n),类似的,给定一个值x,用二分方法将这个值插入一个排好序的数组a,所需要的时间也为O(log n)。插入的同时,也能知晓给定值x在数组a中排第几位。所以,求中位数可等价于求整个数组中的第n/2大的数。
如题,给定两个排好序的数组a和b,他们的数量分别是n和m,取数组a中的中位数,在数组b中进行二分插入,即可该中位数在整个数组和中的位置,然后和要求的位置比较,如果相等,得到答案。如果不等,舍弃a和b数组的一部分,取另一部分继续上述操作即可。
核心思想:
步骤如下:
设初始目标位置(中位值在总数组的位置)为(m+n)/2。
1.取a或b数组中的某数x(为了尽可能每次筛选足够多的数,x应在大数组中取数,并且使比值相同,即 x位置/大数组数量 = 目标位置/数组总数)。
2.在另一个数组中,二分插入x,得到x在该数组中的位置。
3.求得x在总数组中的位置,与目标位置进行比较。
4.如果相同,该值即为中位值。如果不相同,取两数组各取其中一部分,并修改目标位置,返回1。
代码细节:
1.可能出现二分的时候插入值比整个数组都大或者比整个数组都小
2.直接截取数组可能会花费过多时间,因此修改数组begin和end值来截取数组。
3.若为偶数,有两个中位值,可以计算前,拿出数组中的最小值,这样两个中位值变成了一个中位值和中位值前继。
坑点:
1.其中之一数组为空。
2.二分插入时插入值可能比整个数组都大,要特殊讨论。
3.当总数组只有两个数时,去掉最小值会导致答案错误。
代码:
// 二分查找,查找数字target在nums.begin---nums.end之中的位置
// target刚好小于等于nums[return int]
int findTargetPositionInNums(vector<int>& nums, int begin, int end, int target) {
// 如果区间为空,直接返回
if (begin == end) return begin;
int mid = (end + begin) / 2;
while (1) {
// 区间长度小于1,进行比较
if (end <= begin + 1)
if (target > nums[mid]) return end;
else return begin;
// nums[mid - 1] <= nums[mid] < target
if (target > nums[mid]) begin = mid + 1;
// nums[mid - 1] < target <= nums[mid],返回结果
else if (target > nums[mid - 1]) return mid;
// target <= nums[mid - 1] <= nums[mid]
else end = mid;
mid = (end + begin) / 2;
}
}
// 根据总数返回中位值,其中value1 < value2
double getMedian(int value1, int value2, bool isEven) {
return isEven ? (double)(value1 + value2) / 2 : value2;
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int sumSize = nums1.size() + nums2.size(); // 总数
bool isEven = sumSize % 2 == 0; // 判断中位数数量
// 当数量太小
if (sumSize <= 2) {
nums1.insert(nums1.end(), nums2.begin(), nums2.end());
if (isEven) return (double)(nums1[0] + nums1[1]) / 2;
return nums1[0];
}
// 当其中一个数组为空
if (nums1.empty())
return getMedian(nums2[nums2.size() / 2 - 1], nums2[nums2.size() / 2], isEven);
else if (nums2.empty())
return getMedian(nums1[nums1.size() / 2 - 1], nums1[nums1.size() / 2], isEven);
// 正常数组
// 如果是偶数,去掉最小值,使中位数只有一个
if (isEven) {
if (nums1[0] < nums2[0]) nums1.erase(nums1.begin());
else nums2.erase(nums2.begin());
sumSize--;
}
int begin1 = 0;
int end1 = nums1.size();
int begin2 = 0;
int end2 = nums2.size(); // 用于递归的值
int medianPosInSum = sumSize / 2 + 1; // 中位数在总数组中第几大,一开始为sumSize/2+1
int posInBig; // 某数在大数组中的位置
int posInSmall; // 某数在小数组中的位置
int posInSum; // 某数在总数组中的位置
int isInNums1; // 判断最后得到的中位值在nums1还是nums2
while (1) {
if (end1 - begin1 > end2 - begin2) {
// 从大数组中取一个可能的位置,为了降低时间复杂度,取比例位置
posInBig = (float)medianPosInSum / sumSize * (end1 - begin1) + begin1;
// 在小数组中找到相应的位置
posInSmall = findTargetPositionInNums(nums2, begin2, end2, nums1[posInBig]);
// 求出该数在总数组的位置
posInSum = (posInBig - begin1 + 1) + (posInSmall - begin2);
// 将posInSum和medianPosInSum比较
// posInSum < medianPosInSum,砍掉左半部分
if (posInSum < medianPosInSum) {
medianPosInSum -= posInSum;
begin2 = posInSmall;
begin1 = posInBig + 1;
}
// posInSum > medianPosInSum,砍掉右半部分
else if (posInSum > medianPosInSum) {
end2 = posInSmall;
end1 = posInBig;
}
// posInSum == medianPosInSum,得到结果,跳出循环
else {
isInNums1 = true;
break;
}
}
// 对称
else {
posInBig = (float)medianPosInSum / sumSize * (end2 - begin2) + begin2;
posInSmall = findTargetPositionInNums(nums1, begin1, end1, nums2[posInBig]);
posInSum = (posInBig - begin2 + 1) + (posInSmall - begin1);
if (posInSum < medianPosInSum) {
medianPosInSum -= posInSum;
begin1 = posInSmall;
begin2 = posInBig + 1;
}
else if (posInSum > medianPosInSum) {
end1 = posInSmall;
end2 = posInBig;
}
else {
isInNums1 = false;
break;
}
}
}
// 求出中位值和中位值的前一个值
int median = isInNums1 ? nums1[posInBig] : nums2[posInBig];
int preMedian = isInNums1 ? max(nums1[posInBig - 1], nums2[posInSmall - 1])
: max(nums2[posInBig - 1], nums1[posInSmall - 1]);
return getMedian(preMedian, median, isEven);
}