leetcode刷题Day4|寻找两个正序数组的中位数

leetcode刷题Day4|寻找两个正序数组的中位数

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O(log (m+n))

题解:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { 
        int n=nums1.size();
        int m=nums2.size();
        if(n>m){
            return findMedianSortedArrays(nums2,nums1);
        }
        int LMax1,LMax2,RMin1,RMin2,m1,m2,l=0,r=2*n;
        while(l<=r){
            m1=(l+r)/2;
            m2=m+n-m1;
            LMax1 = (m1==0)?INT_MIN:nums1[(m1-1)/2];
            RMin1 = (m1 == 2 * n) ? INT_MAX : nums1[m1 / 2];
						LMax2 = (m2 == 0) ? INT_MIN : nums2[(m2 - 1) / 2];
						RMin2 = (m2 == 2 * m) ? INT_MAX : nums2[m2 / 2];

            if(LMax1>RMin2)
                r=m1-1;
            else if(LMax2>RMin1)
                l=m1+1;
            else break;
            
        }
        
        int LMax=LMax1>LMax2?LMax1:LMax2;
        int RMin=RMin1<RMin2?RMin1:RMin2;
        return (LMax+RMin)/2.0;
    }
};

INT_MAX 是 C/C++ 中的一个宏定义,表示 整型(int)的最大值。它定义在头文件 (C++)或 (C)中。


INT_MAX 的值:

  • 在大多数系统中,INT_MAX 的值为 2147483647
  • 这是因为 int 通常是 32 位有符号整数,其取值范围为:
    • 最小值:-2147483648(即 INT_MIN
    • 最大值:2147483647(即 INT_MAX

为什么使用 INT_MAX

在代码中,INT_MAX 用于初始化变量 RMin1RMin2,表示右半部分的最小值。这是因为:

  1. 虚拟填充

    • 在虚拟填充的数组中,某些位置可能是无效的(例如,超出数组边界)。
    • 为了确保这些无效位置的值不会影响比较逻辑,将它们初始化为 INT_MAX(表示一个非常大的值)。
  2. 比较逻辑

    • 在二分查找中,需要比较左半部分的最大值(LMax1LMax2)和右半部分的最小值(RMin1RMin2)。
    • 如果某个数组的右半部分为空(即没有有效元素),则将 RMin 设置为 INT_MAX,确保它不会影响比较结果。

代码中的具体使用:

RMin1 = (m1 == 2 * n) ? INT_MAX : nums1[m1 / 2];
RMin2 = (m2 == 2 * m) ? INT_MAX : nums2[m2 / 2];
  • 如果 m1 == 2 * n,说明 nums1 的右半部分为空,此时将 RMin1 设置为 INT_MAX
  • 如果 m2 == 2 * m,说明 nums2 的右半部分为空,此时将 RMin2 设置为 INT_MAX

类似宏定义:

  • INT_MIN:表示 int 的最小值(通常是 -2147483648)。
  • LONG_MAX:表示 long 的最大值。
  • LLONG_MAX:表示 long long 的最大值。

m1m2

  • m1nums1 的划分点。
  • m2nums2 的划分点。
  • 由于数组被虚拟填充,m1m2 的关系为:m1 + m2 = m + n

m1=(l+r)/2;

m2=m+n-m1;

虚拟填充(Virtual Padding)是一种技巧,用于简化二分查找的实现,尤其是在处理两个已排序数组的中位数问题时。通过虚拟填充,我们可以将数组的长度扩展为 2n + 1(假设原数组长度为 n),从而使得划分点始终位于数组的“中间”位置。


虚拟填充的目的:

  1. 统一处理奇偶长度

    • 通过虚拟填充,数组的长度总是奇数,这样划分点总是位于中间位置,避免了奇偶长度的特殊处理。
  2. 简化二分查找

    • 虚拟填充后,划分点的计算更加直观,可以直接通过二分查找确定。

虚拟填充的实现:

假设原数组为 nums,长度为 n。虚拟填充后的数组可以表示为:

[#, num[0], #, num[1], #, num[2], ..., #, num[n-1], #]

其中,# 表示虚拟填充的元素。

  • 虚拟填充后的数组长度为 2n + 1
  • 虚拟填充的元素没有实际意义,只是为了方便划分点的计算。

虚拟填充的映射关系:

对于虚拟填充后的数组,划分点 m1 和原数组的索引关系如下:

  • 如果 m1 是偶数,表示划分点位于虚拟填充的位置(#)。
  • 如果 m1 是奇数,表示划分点位于原数组的元素位置。

例如:

  • m1 = 0:划分点位于第一个虚拟填充位置。
  • m1 = 1:划分点位于 nums[0]
  • m1 = 2:划分点位于第二个虚拟填充位置。
  • m1 = 3:划分点位于 nums[1]

代码中的虚拟填充:

LMax1 = (m1 == 0) ? INT_MIN : nums1[(m1 - 1) / 2];
RMin1 = (m1 == 2 * n) ? INT_MAX : nums1[m1 / 2];
LMax2 = (m2 == 0) ? INT_MIN : nums2[(m2 - 1) / 2];
RMin2 = (m2 == 2 * m) ? INT_MAX : nums2[m2 / 2];
解释:
  1. LMax1LMax2

    • 表示左半部分的最大值。
    • 如果 m1 == 0m2 == 0,说明左半部分为空,将 LMax 设置为 INT_MIN
    • 否则,通过 (m1 - 1) / 2(m2 - 1) / 2 计算原数组的索引。
  2. RMin1RMin2

    • 表示右半部分的最小值。
    • 如果 m1 == 2 * nm2 == 2 * m,说明右半部分为空,将 RMin 设置为 INT_MAX
    • 否则,通过 m1 / 2m2 / 2 计算原数组的索引。

示例:

假设 nums1 = [1, 3]nums2 = [2]

虚拟填充后的数组:
  • nums1 虚拟填充后:[#, 1, #, 3, #](长度为 2 * 2 + 1 = 5)。
  • nums2 虚拟填充后:[#, 2, #](长度为 2 * 1 + 1 = 3)。
划分点:
  • 假设 m1 = 2m2 = 1
  • LMax1 = nums1[(2 - 1) / 2] = nums1[0] = 1
  • RMin1 = nums1[2 / 2] = nums1[1] = 3
  • LMax2 = nums2[(1 - 1) / 2] = nums2[0] = 2
  • RMin2 = nums2[1 / 2] = nums2[0] = 2
中位数:
  • LMax = max(LMax1, LMax2) = max(1, 2) = 2
  • RMin = min(RMin1, RMin2) = min(3, 2) = 2
  • 中位数为 (LMax + RMin) / 2.0 = (2 + 2) / 2.0 = 2.0

总结:

  • 虚拟填充通过扩展数组长度,使得划分点的计算更加直观。
  • 虚拟填充后的数组长度为 2n + 1,其中 n 是原数组的长度。
  • 通过虚拟填充,可以统一处理奇偶长度的情况,简化二分查找的实现。

你可能感兴趣的:(leetcode刷题,leetcode,算法,数据结构)