LeetCode之旅(C/C++):4. 两个排序数组的中位数

#PS:不明之处,请君留言,以期共同进步!


##1、题目描述
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 。
请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log (m+n)) 。
你可以假设 nums1 和 nums2 不同时为空。
###示例 1:
nums1 = [1, 3]
nums2 = [2]
中位数是 2.0
###示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
中位数是 (2 + 3)/2 = 2.5

##2、代码实现
###2.1 C++语言
参考文档:【分步详解】两个有序数组中的中位数和Top K问题

//O(log(m+n))
class Solution {
public:
    double findMedianSortedArrays(vector& nums1, vector& nums2) 
    {
        int nums1Size = nums1.size();
        int nums2Size = nums2.size();
        if (nums1Size > nums2Size)   //保证数组1一定最短
            return findMedianSortedArrays(nums2, nums1);
        int L1, L2, R1, R2, C1, C2, low = 0, high = 2 * nums1Size;  //虚拟加了'#'所以数组1是2*n+1长度
        while (low <= high)   //二分
        {
            C1 = (low + high) / 2;  //C1是二分的结果
            C2 = nums1Size + nums2Size - C1; //关键点
            L1 = (C1 == 0) ? INT_MIN : nums1[(C1 - 1) / 2];   //nums1整体比中位数大,只能选L2
            R1 = (C1 == 2 * nums1Size) ? INT_MAX : nums1[C1 / 2];	//nums1整体比中位数小,只能选R2
            L2 = (C2 == 0) ? INT_MIN : nums2[(C2 - 1) / 2];	//nums2整体比中位数大,只能选L1
            R2 = (C2 == 2 * nums2Size) ? INT_MAX : nums2[C2 / 2];	//nums2整体比中位数小,只能选R1
            if (L1 > R2)//C1割太大,左移
                high = C1 - 1;
            else if (L2 > R1)//C1割太小,右移
                low = C1 + 1;
            else//找到合适的割,即在两个数组中,整个左部小于整个右部
                break;
        }
        return (max(L1, L2) + min(R1, R2)) / 2.0;
    }
};

###2.2 C语言(3种)
####第1种

//O(m+n)
//合并两有序数组成一个新有序数组,再按中间位置取值
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) 
{
    int nums12Size = nums1Size + nums2Size;
    //int *nums12[nums12Size]; //C语言不支持
    int *nums12 = (int*)malloc(sizeof(int) * nums12Size);
    int i = 0, j = 0, k = 0;
    while(i < nums1Size && j < nums2Size)
    {
        if(nums1[i] < nums2[j])
        {
            nums12[k] = nums1[i];
            ++i;
            ++k;
        }
        else
        {
            nums12[k] = nums2[j];
            ++j;
            ++k;
        }
    }
    while(i < nums1Size)
    {
        nums12[k] = nums1[i];
        ++i;
        ++k;
    }
    while(j < nums2Size)
    {
        nums12[k] = nums2[j];
        ++j;
        ++k;
    }
    double middle;
    if(nums12Size % 2 == 0)
        middle = (nums12[nums12Size / 2 - 1] + nums12[nums12Size / 2]) * 1.0 / 2;
    else
        middle = nums12[nums12Size / 2];
    return middle;
}

####第2种

//O(k)
//两个指针分别从两数组开头指,比较两指针处的数,谁小谁往后移,
//直到两指针共移动k次,k为中间位置
//可以理解成当前下标前面的元素都是归并有序的,而不包括当前下标所指向的元素
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) 
{
	int nums12Size = nums1Size + nums2Size;
	int index1 = 0; //移动次数和下标,刚好是一一对应关系
	int index2 = 0; 
	double middle = 0;
	while (index1 + index2 < (nums12Size + 2) / 2 ) //移动次数之和小于K
	{
		if (index1 < nums1Size) //移动数组1
		{
			while (index2 == nums2Size || nums1[index1] <= nums2[index2]) 
			{
				index1++;
                //无论奇偶都用中间的两个数计算中位数,只是奇数时中间的两个数在同一个位置
				if (index1 + index2 == (nums12Size + 1) / 2)
				{
					middle += nums1[index1 - 1];
				}
				if (index1 + index2 == (nums12Size + 2) / 2)
				{
					middle += nums1[index1 - 1];
				}
				if (index1 == nums1Size) //数组1整体比中位数小
				{
					break;
				}
			}
		}
		if (index2 < nums2Size) //移动数组2
		{
			while (index1 == nums1Size || nums2[index2] <= nums1[index1]) 
			{
				index2++;
				if (index1 + index2 == (nums12Size + 1) / 2)
				{
					middle += nums2[index2 - 1];
				}
				if (index1 + index2 == (nums12Size + 2) / 2)
				{
					middle += nums2[index2 - 1];
				}
				if (index2 == nums2Size) //数组2整体比中位数小
				{
					break;
				}
			}
		}
	}
	return middle / 2;
}

####第3种

//O(log(m+n))
//两个数组分别采用二分法查找
//寻找第K小的数
//与C++代码的二分有所区别
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size)
{
	int nums12Size = nums1Size + nums2Size;
	int left = (nums12Size + 1) / 2;	//左中位数的位置,位置是从1开始的,下标是从0开始的
	int right = (nums12Size + 2) / 2;	//右中位数的位置
	if (left == right)	//奇数
	{
		return getKMin(nums1, nums1Size, 0, nums2, nums2Size, 0, left);
	}
	else//偶数
	{
		return (getKMin(nums1, nums1Size, 0, nums2, nums2Size, 0, left) + \
			getKMin(nums1, nums1Size, 0, nums2, nums2Size, 0, right)) * 1.0 / 2;
	}
}

//注意:k和数组都是递归变化的
int getKMin(int* nums1, int nums1Size, int nums1Start, int* nums2, int nums2Size, int nums2Start, int k)
{
	if (nums1Start > nums1Size - 1) //数组1整体比中位数小,中位数在数组2中
	{
		return nums2[nums2Start + k - 1];
	}
	if (nums2Start > nums2Size - 1) //数组2整体比中位数小,中位数在数组1中
	{
		return nums1[nums1Start + k - 1];
	}
	if (k == 1) //要找的是第1小的数,分两种情况,一种是两个数组共有2个以内的元素,另一种是递归到k=1
	{
		return nums1[nums1Start] < nums2[nums2Start] ? nums1[nums1Start] : nums2[nums2Start];
	}
	int nums1Min = INT_MAX, nums2Min = INT_MAX;
	if (nums1Start + k / 2 - 1 < nums1Size) //当一个数组很短,比中位数的位置还短时,就先不管它
	{
		nums1Min = nums1[nums1Start + k / 2 - 1];
	}
	if (nums2Start + k / 2 - 1 < nums2Size) 
	{
		nums2Min = nums2[nums2Start + k / 2 - 1];
	}
    //每次都从“较小”的数组中“删掉”前k/2个元素,直到将其中的一个数组全部删掉,或者k=1时,就能在下一次递归中找到目标元素
	return nums1Min < nums2Min ? \
		getKMin(nums1, nums1Size, nums1Start + k / 2, nums2, nums2Size, nums2Start, k - k / 2) : \
		getKMin(nums1, nums1Size, nums1Start, nums2, nums2Size, nums2Start + k / 2, k - k / 2);
}

你可能感兴趣的:(LeetCode之旅)