LeetCode 4.寻找两个有序数组的中位数 Median of Two Sorted Arrays (C语言)

题目描述:

给定两个大小为 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

题目解答:

方法1:暴力法

重新申请一个m + n长的数组,将两个数组的元素按从小到大的顺序放到新数组中,然后直接求中位数,但时间复杂度为O(m + n),不符合题目要求。运行时间也可以到达最快24ms,代码如下。

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int m = nums1Size, n = nums2Size;
    int* t = (int*)malloc((m + n) * sizeof(int));
    int i = 0, j = 0, index = 0;
    int left = 0, right = 0;
    double result = 0;
    while(index < m + n) {
        left = (i < m ? nums1[i] : INT_MAX);
        right = (j < n ? nums2[j] : INT_MAX);
        if(left < right)
            t[index++] = nums1[i++];
        else 
            t[index++] = nums2[j++];
    }
    if((m + n) & 1)
        result =  t[(m + n) / 2];
    else
        result = (t[(m + n) / 2] + t[(m + n) / 2 - 1]) / 2.0 ;
    free(t);
    return result;
}

方法2:二分查找

易知二分法或者二叉树相关算法的时间复杂度为O(log(n)),而题目要求O(log(m + n)),则可能会用到其中一个,没有二叉树,所以本题可能就是用二分法。问题就是确定子问题。
根据中位数的定义,中位数的左右两侧数字个数相同,且其左边的数字比其小,右边的数字比其大。构造两个子集,分别是左右子集,假定取nums1中的前i (i ∈[0, m])个放在左子集,nums2的前j (j ∈[0, n])个放在左子集,此时左子集数字个数为i + j,右子集数字个数为m + n - i - j。关系是:

i与j关系 m + n情况 中位数
i + j = m + n - i - j m + n 为偶数 中位数为左侧最大值与右侧最小值的平均值, (max_left + min_right) / 2
i + j = m + n + 1 - i - j m + n 为奇数 中位数放在左子集,即左侧最大值 max_left

假设我们遍历 i,偶数时为j = (m + n) / 2 - i,奇数时为j = (m + n + 1) / 2 - i,但如果m > n,j会为负数,所以要求m <= n。其实偶数时 j 表示成奇数时的式子也是可以的,因为C语言里边的除法是整除,所以加上1不会影响j的结果。所以j = (m + n + 1) / 2 - im <= n
另一个要求是:

A[i - 1] <= B[j]
B[j - 1] <= A[i]

i = (begin + end) / 2

  • 如果A[i - 1] > B[j],则说明需要减小i,减小i的同时j也会增大,这样A[i - 1]的值就会减小,B[j]的值会增大,向着满足条件的方向靠近。而且因为从i到end之间A是递增的,所以i到end之间的都不符合(i越大,A[i - 1]越大,B[j]越小),故直接将end置为i - 1
  • 如果B[j - 1] > A[i],则说明需要减小j,减小j的意味着增大i,这样A[i]的值就会增大,B[j + 1]的值会减小,向着满足条件的方向靠近。而且因为从begin到i之间A是递增的,所以begin到i之间的都不符合(i越小,A[i - 1]越小,B[j]越大),故直接将begin置为i + 1

如果两个条件都满足说明已经遍历到正确的中间位置,进行后续逻辑判断即可。需要注意的是边界情况,在判断时一定要保证数组索引在范围之内,对于不符合的情况,进入到最终的判断逻辑中进行处理。运行时间24ms,代码如下。

#define min(a, b) (a < b ? a : b)
#define max(a, b) (a > b ? a : b)
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int m = nums1Size, n = nums2Size;
    int t = 0;
    if(m > n) {
        int* temp = nums1;
        nums1 = nums2;
        nums2 = temp;
        t = m;
        m = n;
        n = t;
    }
    int i = 0, j = 0, left = 0, right = 0;
    int begin = 0, end = m;
    t = (m + n + 1) / 2;
    while(begin <= end) {
        i = (begin + end) / 2;
        j = t - i;
        if(i > 0 && j < n && nums1[i - 1] > nums2[j])
            end = i - 1;
        else if(j > 0 && i < m && nums2[j - 1] > nums1[i])
            begin = i + 1;
        else {
            if(i == 0)
                left = nums2[j - 1];  
            else if(j == 0)
                left = nums1[i - 1];
            else
                left = max(nums1[i - 1], nums2[j - 1]);
            if(i == m)
                right = nums2[j];
            else if(j == n)
                right = nums1[i];
            else 
                right = min(nums1[i], nums2[j]);
            if((m + n) & 1)
                return left;
            else
                return (left + right) / 2.0;
        } 
    }
    return 0;
}

你可能感兴趣的:(LeetCode,C语言)