leetcode 4.Median of two sorted arrays

leetcode 4.Median of two sorted arrays

解法1:双指针合并序列(O(m+n))

 最朴素的做法,可以做一点点小小的优化:

我们不需要记录合并后的数组,只需要用一个变量记录当前的下标位置即可,执行到中间时直接return即可。

但是因为太久没有写算法题,在一些边界问题上还是浪费了一些时间。

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int lens = nums1.size() + nums2.size();
        int end_point = lens/2 + 1;
        int ans = 0;
        int t1 = INT_MAX, t2 = INT_MAX;
        // now_count已添加的个数
        for(int i = 0, j = 0, now_count = 0; now_count < end_point;)
        {
            if(i < nums1.size() && j < nums2.size())
            {
                if(nums1[i] <= nums2[j])
                    t1 = nums1[i++];
                else 
                    t1 = nums2[j++];
            }
            else
            {
                if(i < nums1.size())
                    t1 = nums1[i++];
                else
                    t1 = nums2[j++];
            }
            now_count++;
            if(now_count == end_point - 1 && now_count > 0)
            {
                ans = ans + t1;
                t2 = t1;
            }
            else if(now_count == end_point)
                ans = ans + t1;
        }
        if((end_point << 1) - lens == 1)
        {
            if(t2 != INT_MAX)
                ans = ans - t2;
            return ans;
        }
        else
            return (double)ans/2;
    }
};

避免数组越界

这里要注意,nums1和nums2都有可能为空,所以在判断nums1[i]和nums2[j]取哪个的时候,不能直接这样写:

if(i < nums1.size() && nums1[i] <= nums2[j])
            {
                t1 = nums1[i];
                i++;
            }
            else if(j < nums2.size())
            {
                t1 = nums2[j];
                j++;
            }

当nums2为空的时候,这里就会越界报错了。

小心只需要取一个数的情况

if(now_count == end_point - 1 && now_count > 0)
            {
                ans = ans + t1;
                t2 = t1;
            }
if((end_point << 1) - lens == 1)
        {
            if(t2 != INT_MAX)
                ans = ans - t2;
            return ans;
        }

这两处特判都是为了处理只需要取一个数的情况。

 解法2:正常二分(O(log(min(m,n))))

这种二分做法比题目给出的目标复杂度还要快,说实话,我反而想不到O(log(m+n))要怎么做,因为两个序列如果都单独来看,都是具有单调性、可二分的,但如果放在一起的话这种单调性就消失了。

 

class Solution
{
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
    {
        // 保证nums1是较短的数组
        if(nums1.size() > nums2.size())
            return findMedianSortedArrays(nums2, nums1);
        int m = nums1.size(), n = nums2.size();
        int l = -1, r = m - 1;
        int cnt = (m + n + 1)/2; // cnt是个数, 向上取整
        int nums1_less, nums1_more, nums2_less, nums2_more;
        while(l <= r)
        {
            int mid = l + r >> 1, mid2 = cnt - mid - 2; // mid是下标,cnt - (mid + 1)算出来是个数,再减一转换成下标mid2
            nums1_less = (mid < 0) ? INT_MIN : nums1[mid];
            nums1_more = (mid + 1 < m) ? nums1[mid + 1] : INT_MAX;
            nums2_less = (mid2 < 0 || !n) ? INT_MIN : nums2[mid2];
            nums2_more = (mid2 + 1 < n) ? nums2[mid2 + 1] : INT_MAX;
            // cout << mid << " " << mid2 << endl;
            if(nums1_less <= nums2_more && nums1_more >= nums2_less)
            {
                // cout << mid << " " << mid2 << endl;
                if(m + n & 1) // 奇数
                    return max(nums1_less, nums2_less);
                else
                    return ((double)max(nums1_less, nums2_less) + min(nums1_more, nums2_more)) / 2;
            }
            else if(nums1_less < nums2_less)
                l = mid + 1;
            else    
                r = mid - 1;
        }
        return 233333;
    }
};

二分的具体策略是二分答案,在较短的数组中选一个位置,假定中位数就在这里,然后去验证条件是否满足。

平时做的二分都是在分割点右侧所有点都满足答案条件,左边全都不满足;

但是这道题比较特殊,这道题是只有一个点满足答案,左右两边都不满足,所以循环结束的条件是l<=r;而且一旦找到满足条件的点就要结束循环。

因为nums1可能一个都不选,所以l要设置为一个越界值,即-1。

解法三:伪二分

// 伪二分做法
class Solution
{
public:
    double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2)
    {
        int m = nums1.size(), n =nums2.size();
        if(m > n)
            return findMedianSortedArrays(nums2, nums1);
        int total_nums = m + n;
        if(total_nums & 1)
            return find_kth_in_array(nums1, nums2, total_nums/2 + 1);
        else
            return (find_kth_in_array(nums1, nums2, total_nums/2) + find_kth_in_array(nums1, nums2, total_nums/2 + 1)) / 2;
    }
    double find_kth_in_array(std::vector<int>& nums1, std::vector<int>& nums2, int k)
    {
        int idx1 = 0, idx2 = 0;
        int m = nums1.size(), n = nums2.size();
        while(true)
        {
            if(idx1 >= m)
                return nums2[idx2 + k - 1];
            if(idx2 >= n)
                return nums1[idx1 + k - 1];
            if(k == 1)
            {
                return std::min(nums1[idx1], nums2[idx2]);
            }
            int k1 = k/2, k2 = k - k1;
            int new_idx1 = std::min(m - 1, idx1 + k1 - 1), new_idx2 = std::min(n - 1, idx2 + k2 - 1);
            int p1 = nums1[new_idx1], p2 = nums2[new_idx2];
            if(p1 <= p2)
            {
                k -= new_idx1 - idx1 + 1;
                idx1 = new_idx1 + 1;
            }
            else
            {
                k -= new_idx2 - idx2 + 1;
                idx2 = new_idx2 + 1;
            }
        }
    }
};

注意特判要把k==1的情况放在最后,因为nums1有可能是空的,如果把k==1的情况放在第一个,就可能出现越界的情况。

这个做法的核心逻辑是,如果p1<=p2,那么p1以及它左边的数都太小了,不可能成为最后的答案,就把他们都舍弃掉,类似于二分答案的思想,而且更新k的过程这个操作也类似于二分,k衰减的速度和传统二分是差不多的。

posted @ 2024-08-06 23:22  Gold_stein  阅读(4)  评论(0编辑  收藏  举报