Day5——LeetCode4.寻找两个正序数组的中位数&2364.统计环数对的数目

1 前言

  本来想按算法专题进行刷题,所以今天打算先从分治算法开始。于是选择了分治标签下的题目,看到一个寻找两个正序数组中位数的题,看题目感觉和在算法课上课后题差不多,而且此题还标注困难,遂点开开刷。

2 寻找两个正序数组的中位数(LeetCode4)

2.1 题目描述

  给定两个大小分别位mn的正序数组nums1nums2。返回两个正序数组合并后的中位数,要求算法的时间复杂度为 O ( log ⁡ ( m + n ) ) O(\log(m+n)) O(log(m+n))
  示例:
1

2.2 题目分析与解决

  要寻找合并后数组的中位数,即找两个数组中第 ( m + n ) / 2 (m+n)/2 (m+n)/2或者第 ( m + n ) / 2 − 1 , ( m + n ) / 2 (m+n)/2-1,(m+n)/2 (m+n)/21,(m+n)/2小的数。(分别对应总数为奇偶的情况,方便起见以下统称为 k k k k − 1 , k k-1,k k1,k
  首先我们可以定义两个指针分别指向两个数组的头,然后不断选择指向较小的数的指针并且移动该指针,直到操作 k k k次,这样我们便能找到第 k k k小的数或者第 k − 1 , k k-1,k k1,k小的数。注意由于两个数组不一定等长,因此要处理一个数组完全遍历完的情况,且要记录前一次的数。总之有许多细节需要注意。但该算法的时间复杂度为 O ( m + n ) O(m+n) O(m+n),显然不符合题意。
  另一种思路是分治的思想,我们先考虑等长的情况,设nums1[n/2]= m 1 m_1 m1nums2[n/2]的= m 2 m_2 m2。则两个数组分别别其中位数划分成两个部分:
nums1 = p 1 ∣ ∣ m 1 ∣ ∣ p 2 nums2 = q 1 ∣ ∣ m 2 ∣ ∣ q 2 \text{nums1}=p_1||m_1||p_2\\\text{nums2}=q_1||m_2||q_2 nums1=p1∣∣m1∣∣p2nums2=q1∣∣m2∣∣q2  考虑两个数组的中位数大小,若 m 1 = m 2 m_1=m_2 m1=m2,则中位数就是 m 1 m_1 m1 m 2 m_2 m2。若 m 1 < m 2 m_1<m_2 m1<m2,则 p 1 < m 1 < m 2 < q 2 p_1<m_1<m_2<q_2 p1<m1<m2<q2,所以 p 1 p_1 p1 q 2 q_2 q2中一定不会有合并后的中位数,合并后的中位数只能在 m 1 ∣ ∣ p 2 m_1||p_2 m1∣∣p2 q 1 ∣ ∣ m 2 q_1||m_2 q1∣∣m2中,这样问题的规模就被我们减少了一般。另一种情况也类似。注意,这里偶数个数的中位数是两个数的平均值,因此偶数个数情况还有许多细节需要考虑,这里只是介绍思路。
  上述的情况是一种特殊情况,我刷的算法课后题也是这种情况,本来以为可以秒,结果想了半天。
  这里我们看一般情况。考虑中位数的定义:即将数组划分为两个相等的部分,一部分比中位数小,一部分比中位数大。所以我们考虑将两个数组进行划分:
nums1=nums1 [ 0 : i − 1 ] ( l 1 )   ∣ ∣  nums1 [ i : m − 1 ] ( r 1 ) nums2=nums2 [ 0 : j − 1 ] ( l 2 )   ∣ ∣  nums2 [ j : n − 1 ] ( r 2 ) \text{nums1=nums1}[0:i-1](l_1) \ ||\ \text{nums1}[i:m-1](r_1)\\\text{nums2=nums2}[0:j-1](l_2) \ ||\ \text{nums2}[j:n-1](r_2) nums1=nums1[0:i1](l1) ∣∣ nums1[i:m1](r1)nums2=nums2[0:j1](l2) ∣∣ nums2[j:n1](r2)其中 0 ≤ i ≤ m , 0 ≤ j ≤ n 0\leq i\leq m,0\leq j\leq n 0im,0jn i = 0 i=0 i=0 nums [ 0 : i − 1 ] \text{nums}[0:i-1] nums[0:i1]为空集,其他边界情况同理。

  由上述划分可以知道, l 1 < r 1 , l 2 < r 2 l_1<r_1,l_2<r_2 l1<r1,l2<r2。我们的目标是找到这样一组划分 i , j i,j i,j

  • m + n m+n m+n为偶数,则 l 1 + l 2 = r 1 + r 2 l_1+l_2=r_1+r_2 l1+l2=r1+r2,即 i + j = m − i + n − j i+j=m-i+n-j i+j=mi+nj;若 m + n m+n m+n为奇数,则 i + j = m − i + n − j + 1 i+j=m-i+n-j+1 i+j=mi+nj+1。所以 i + j = ( m + n + 1 ) / 2 i+j=(m+n+1)/2 i+j=(m+n+1)/2。(C++中是下取整,奇数和偶数情况均符合)
  • max { l 1 } < min { r 2 } , max { l 2 } < min { r 1 } \text{max}\{ l_1\}<\text{min}\{r_2\},\text{max}\{l_2\}<\text{min}\{r_1\} max{l1}<min{r2},max{l2}<min{r1},即 l 1 l_1 l1 l 2 l_2 l2所有的数都要小于 r 1 r_1 r1 r 2 r_2 r2中的数。

  综合上述两条,若 m + n m+n m+n为偶数,则中位数为 l 1 , l 2 l_1,l_2 l1,l2中的最大值与 r 1 , r 2 r_1,r_2 r1,r2中的最小值的平均值。若 m + n m+n m+n为奇数,则中位数为 l 1 , l 2 l_1,l_2 l1,l2中的最大值。(因为 l 1 , l 2 l_1,l_2 l1,l2 r 1 , r 2 r_1,r_2 r1,r2元素多1)
  因此问题转化为如何找到这样的一组划分 i , j i,j i,j。因为 i + j = ( m + n + 1 ) / 2 i+j=(m+n+1)/2 i+j=(m+n+1)/2,所以 j = ( m + n + 1 ) / 2 − i j=(m+n+1)/2-i j=(m+n+1)/2i。这里要注意, i ∈ [ 0 , m ] , j ∈ [ 0 , n ] i\in[0,m],j\in[0,n] i[0,m],j[0,n],因此必须令 m < n m<n m<n,这样不断选择 i i i时, j = ( m + n + 1 ) / 2 − i ∈ [ 0 , n ] j=(m+n+1)/2-i\in[0,n] j=(m+n+1)/2i[0,n]。那么如何选择 i i i呢,由于要求时间复杂度为 O ( log ⁡ ( m + n ) ) O(\log(m+n)) O(log(m+n)),因此可以借助二分查找的思路,每次选择合法区间的中间值作为 i i i判断是否满足条件:

  • 初始 left = 0 , right=m , i = (left+right) / 2 , j = ( m + n + 1 ) / 2 − i \text{left}=0,\text{right=m},i=\text{(left+right)}/2,j=(m+n+1)/2-i left=0,right=m,i=(left+right)/2,j=(m+n+1)/2i
  • 若此时符合上述第二点条件,则找到了这样的划分,进而可以求出中位数。
  • max { l 1 } > min { r 2 } \text{max}\{ l_1\}>\text{min}\{r_2\} max{l1}>min{r2},说明 nums1 [ i − 1 ] \text{nums1}[i-1] nums1[i1]太大,我们需要移动右区间找更小的 nums1 [ i − 1 ] \text{nums1}[i-1] nums1[i1] right ← i − 1 \text{right}\leftarrow i-1 righti1
  • max { l 2 } > min { r 1 } \text{max}\{l_2\}>\text{min}\{r_1\} max{l2}>min{r1},说明 nums1 [ i ] \text{nums1}[i] nums1[i]太小,我们需要移动左区间找更大的 nums1 [ i ] \text{nums1}[i] nums1[i]
    left ← i + 1 \text{left}\leftarrow i+1 lefti+1

  这样我们就能求出合并后数组的中位数,时间复杂度为 O ( log ⁡ ( min ⁡ ( m , n ) ) ) O(\log(\min(m,n))) O(log(min(m,n))),空间复杂度为 O ( 1 ) O(1) O(1)。具体实现代码如下:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        
        int m=nums1.size(),n=nums2.size();
        //使nums1是短的那一个
        if(m>n){
            return findMedianSortedArrays(nums2,nums1);
        }

        int left=0,right=m;
        //每次缩小一半区间
        while(left<=right){
            //寻找nums1的划分位置
            int nums1_partition=(left+right)/2;
            //此时nums2的划分位置
            int nums2_partition=(m+n+1)/2-nums1_partition;
            //nums1和nums2左边的最大值
            //若左部分是空集令其为无穷小,这样不影响两个左部分的最大值
            int nums1_left_max=(nums1_partition==0)?INT_MIN:nums1[nums1_partition-1];
            int nums2_left_max=(nums2_partition==0)?INT_MIN:nums2[nums2_partition-1];
            //nums1和nums2右边的最小值
            //若右部分为空集则令其为无穷大,这样不影响两个右部分的最小值
            int nums1_right_min=(nums1_partition==m)?INT_MAX:nums1[nums1_partition];
            int nums2_right_min=(nums2_partition==n)?INT_MAX:nums2[nums2_partition];

            //符合划分条件
            if(nums1_left_max<=nums2_right_min&&nums2_left_max<=nums1_right_min){
                //根据奇偶情况计算中位数
                return (m+n)%2==0? (max(nums1_left_max,nums2_left_max)+min(nums1_right_min,nums2_right_min))/2.0:max(nums1_left_max,nums2_left_max);
            }
            //nums1划分值太大,从原区间的左一半选
            else if(nums1_left_max>nums2_right_min){
                right=nums1_partition-1;
            }
            //nums1划分值太小,从原区间的右一半选
            else{
                left=nums1_partition+1;
            }
        }
        return 0.0;
    }
};

3 统计坏数对的数目(LeetCode2364)

3.1 题目描述

  偷个懒,看图:
2

3.2 问题分析与解决

  这里和之前一道好的子数组的题差不多。筛选的分治题目居然给了一道哈希表。
  如果不是坏数对,则满足 i < j , j − i = n u m s [ j ] − n u m s [ i ] i<j,j-i=nums[j]-nums[i] i<j,ji=nums[j]nums[i],也就是 n u m s [ j ] − j = n u m s [ i ] − i nums[j]-j=nums[i]-i nums[j]j=nums[i]i,因此我们只需要遍历数组 n u m s [ i ] nums[i] nums[i],考虑 n u m s [ i ] nums[i] nums[i]与其前面的数组成的好数对的个数,然后用 n u m s [ i ] nums[i] nums[i]与其前面的数组成的对数减去好数对的个数,最后将所有的 n u m s [ i ] nums[i] nums[i]得到的结果累加即可。
   n u m s [ i ] nums[i] nums[i]可与其前面的数组成 i i i对, n u m s [ i ] nums[i] nums[i]可与其前面组成的好数对的个数为前面的 n u m s [ i ] − i nums[i]-i nums[i]i出现的次数,想到这一点就很好解决了:

class Solution {
public:
    long long countBadPairs(vector<int>& nums) {
        map<int,int> hash;
        long long ans=0;
        long long n=nums.size();
        for(int i=0;i<n;i++){
            ans+=(i-hash[nums[i]-i]);
            hash[nums[i]-i]++;
        }
        return ans;    
    }
};

  时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值