中位数
中位数(Median)又称中值,统计学中的专有名词,是按顺序排列的一组数据中居于中间位置的数,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。
解题
现在有两种思路:
1.将两个数组合并成一个有序数组,合并后找到中位数;
2.在两个数组中遍历,找到中位数;因为合并后的数组是一个有序(升序)的数组,则通过二分查找更为快捷,这里就先使用遍历了。
思路1解题过程
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int> merge_nums;
merge(merge_nums, nums1, nums2);
double middle = 0;
if ((merge_nums.size() % 2) == 0) {
middle = merge_nums[merge_nums.size() / 2] / 2.0 + merge_nums[merge_nums.size() / 2 + 1] / 2.0;
}
else {
middle = merge_nums[merge_nums.size() / 2];
}
return middle;
}
void merge(vector<int>& resutl, vector<int>& nums1, vector<int>& nums2) {
auto it_nums1 = nums1.begin();
auto it_nums2 = nums2.begin();
while (it_nums1 != nums1.end() && it_nums2 != nums2.end()) {
if (*it_nums1 < *it_nums2) {
resutl.push_back(*it_nums1);
resutl.push_back(*it_nums2);
}
else {
resutl.push_back(*it_nums2);
resutl.push_back(*it_nums1);
}
it_nums1++;
it_nums2++;
}
if (it_nums1 != nums1.end()) {
resutl.insert(resutl.end(), it_nums1, nums1.end());
}
if (it_nums2 != nums2.end()) {
resutl.insert(resutl.end(), it_nums2, nums2.end());
}
}
};
仔细审查代码后发现:
1.while循环中两个迭代器的增加有问题,每次循环只能放入一个值;
2.计算中位数时,偶数的中位数计算有问题,不应是a[size/2]+a[size/2+1]
而是a[size/2]+a[size/2-1]
;
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int> merge_nums;
merge(merge_nums, nums1, nums2);
double middle = 0;
if ((merge_nums.size() % 2) == 0) {
middle = merge_nums[merge_nums.size() / 2] / 2.0 + merge_nums[merge_nums.size() / 2 - 1] / 2.0;
}
else {
middle = merge_nums[merge_nums.size() / 2];
}
return middle;
}
void merge(vector<int>& resutl, vector<int>& nums1, vector<int>& nums2) {
auto it_nums1 = nums1.begin();
auto it_nums2 = nums2.begin();
while (it_nums1 != nums1.end() && it_nums2 != nums2.end()) {
if (*it_nums1 < *it_nums2) {
resutl.push_back(*it_nums1);
it_nums1++;
}
else {
resutl.push_back(*it_nums2);
it_nums2++;
}
}
if (it_nums1 != nums1.end()) {
resutl.insert(resutl.end(), it_nums1, nums1.end());
}
if (it_nums2 != nums2.end()) {
resutl.insert(resutl.end(), it_nums2, nums2.end());
}
}
};
修改后可以通过;
思路2解题过程
1.维护两个迭代器遍历两个vector
;
2.首先判断参与中位数计算的操作数的格式,为偶数个还是奇数个;
3.调用函数findMedian
根据中位数位置,遍历两个数组,找到指定位置的值;同时将两个迭代器中的较小值的迭代器向后移动,方便在第二次调用函数findMedian
时,定位第二个操作数的值;
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
bool is_even_numbers = (nums1.size() + nums2.size()) % 2 == 0;
int pos_first = is_even_numbers ? (nums1.size() + nums2.size()) / 2 - 1 : (nums1.size() + nums2.size()) / 2;
auto it_num1 = nums1.begin();
auto it_num2 = nums2.begin();
int count = 0;
if (is_even_numbers) {
double first = findMedian(nums1, nums2, it_num1, it_num2, pos_first, count);
double secode = findMedian(nums1, nums2, it_num1, it_num2, pos_first + 1, ++count);
return (first + secode) / 2.0;
}
else {
return findMedian(nums1, nums2, it_num1, it_num2, pos_first, count);
}
}
double findMedian(vector<int>& nums1, vector<int>& nums2,
vector<int>::iterator& it1, vector<int>::iterator& it2, int pos, int& count) {
double result = 0;
while (it1 != nums1.end() && it2 != nums2.end()) {
if (count == pos) {
if (*it1 < *it2) {
result = *it1;
++it1;
}
else {
result = *it2;
++it2;
}
return result;
}
*it1 < *it2 ? ++it1 : ++it2;
++count;
}
while (it1 != nums1.end()) {
if (count == pos) {
result = *it1;
++it1;
return result;
}
++it1;
++count;
}
while (it2 != nums2.end()) {
if (count == pos) {
result = *it2;
++it2;
return result;
}
++it2;
++count;
}
return result;
}
};
以上代码还可以改进为在某一个数组遍历结束时,中位数剩余的计数直接在剩余数组内的迭代器偏移计算得到,不再使用循环;
if (it1 != nums1.end()) {
it1 = it1 + pos - count;
count = pos;
result = *it1;
++it1;
}
思路3解题过程
根据两个数组个数和是否为偶数,可以得到参与中位数计算的操作数是一个还是两个;
使用left和right,记录相邻的两个数值,如果是偶数个就(left + right)/2.0
;如果是奇数就是right
;
使用迭代器:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int left = 0;
int right = 0;
int end = (nums1.size() + nums2.size()) / 2;
auto it1 = nums1.begin();
auto it2 = nums2.begin();
int count = 0;
while (count <= end) {
left = right;
if (it1 != nums1.end() && (it2 == nums2.end() || *it1 < *it2))
{
right = *it1;
it1++;
}
else {
right = *it2;
it2++;
}
count++;
}
return (nums1.size() + nums2.size()) % 2 == 0 ? (left + right) / 2.0 : right;
}
};
使用下标索引
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int left = 0;
int right = 0;
int end = (nums1.size() + nums2.size()) / 2;
int pos_nums1 = 0;
int pos_nums2 = 0;
for (int i = 0; i <= end; i++) {
left = right;
if (pos_nums1 < nums1.size()
&& (pos_nums2 >= nums2.size() || nums1[pos_nums1] < nums2[pos_nums2]))
{
right = nums1[pos_nums1++];
}
else {
right = nums2[pos_nums2++];
}
}
return ((nums1.size() + nums2.size()) & 1) == 0 ? (left + right) / 2.0 : right;
}
};
思路4解题过程
考虑一种特殊的情况,即一个数组内的数值都比另外一个数组的最大值还要大。那么根据两个数组的长度,直接定位到中位数所在的位置;
不考虑以上的特殊情况时,找中位数就是找找到合并数组后位于中间位置的数或者两数之和;如果不考虑合并,那么就需要从两个数组中去找第len/2
位置的数;之前的在两个数组的遍历都是在寻找这个第len/2
数;另外也可以换种思路,去排除前len/2-1
个数;
LeetCode的官方给出第一个解题思路就是二分法查找;
其中有这么一句话:如何把时间复杂度降低到 O(log(m+n))
呢?如果对时间复杂度的要求有 log
,通常都需要用到二分查找,这道题也可以通过二分查找实现。
二分法是指要排除的前k-1个数,分为两部分,用来部分的排除;
如果我们要找到第k个数,现在有两个有序的集合,那我们就会先看看每个集合中k/2-1位置的数相比较,排除较小的数以及前面的所有数,这样就可以排除k/2个数据,然后再在两个集合中对比k/2/2-1位置的数,依次排除;到最后将排除k-1个数,那么就会找到第k个数了。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int total_len = nums1.size() + nums2.size();
if (total_len % 2 == 1) {
return getKthElement(nums1, nums2, (total_len + 1) / 2);
}
else {
return (getKthElement(nums1, nums2, (total_len + 1) / 2)
+ getKthElement(nums1, nums2, total_len / 2)) / 2.0;
}
}
int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {
int m = nums1.size();
int n = nums2.size();
int index1 = 0, index2 = 0;
while (true)
{
// 当nums1集合以遍历完时,直接从nums2中找到K位置的值;
if (index1 == m) {
return nums2[index2 + k - 1];
}
// 当nums2集合以遍历完时,直接从nums1中找到K位置的值;
if (index2 == n) {
return nums1[index1 + k - 1];
}
// 当K收缩到1时,从两个当前位置找一个最小值返回;
if (k == 1) {
return min(nums1[index1], nums2[index2]);
}
int next_index1 = min(index1 + k / 2 - 1, m - 1);
int next_index2 = min(index2 + k / 2 - 1, n - 1);
// 当k大于1时
if (nums1[next_index1] <= nums2[next_index2]) {
k -= next_index1 - index1 + 1;
index1 = next_index1 + 1;
}
else {
k -= next_index2 - index2 + 1;
index2 = next_index2 + 1;
}
}
}
};