六月集训(08)前缀和

1.LeetCode:1838. 最高频元素的频数

原题链接


        元素的 频数 是该元素在一个数组中出现的次数。

        给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。

        执行最多 k 次操作后,返回数组中最高频元素的 最大可能频数 。

        示例 1:

        输入:nums = [1,2,4], k = 5

        输出:3

        示例 2:

        输入:nums = [1,4,8,13], k = 5

        输出:2

        示例 3:

        输入:nums = [3,9,6], k = 2

        输出:1

        提示:

        1 <= nums.length <= 1e5

        1 <= nums[i] <= 1e5

        1 <= k <= 1e5


        这里先对原数组进行排序,并求前缀和。然后我们枚举每一个元素,将他作为要被改变的数字,由于是按照升序排序,题目要求我们只能吧数组元素+1,所以i位置之后的元素在这次操作中无需考虑。接着开始滑动窗口,左边界为l(初始化为0),右边界为i具体操作下面再解释。

        在进行操作前先对前缀和数组的下标进行解释,这里十分关键。首先,由于我们在求前缀和的时候,吧所有元素的下标都向右移动了一位,而为了求取前缀和方便,以下除了窗口的左边界l对应着原数组的下标,其他下标均对应着他原始下标+1.这样偏移坐标的好处是当我们枚举原数组中i-1,现在为i下标的元素的时候,要求j(原始下标)到他的范围和,只需要prev[i]-prev[j]即可,求长度也只需要i-j.(i-1-j+1)


        具体操作为,枚举数组所有元素,为了求取前缀和方便我们这里的下标都右移一位,称其为 j 。那么他可以表示为nums[j-1],也可以是prev[j]-prev[j-1],下面为了方便就用nums[j-1]。现在我们吧他作为了指标,那么能被改变的数字就只能是窗口内他之前的数字,也就是[l,j-2]这个范围内的数字。那么改变之后这些数字的和就是 j - l ([ j-2-l+1]+1),减去这个范围内之前的和:prev[j]-prev[l],(之前提到过的求某部分范围和的方法)就是改变前后和的差值,也就是这次操作要改变的次数,如果大于了k就一直右移左边界,直到合法位置。最后维护ans

class Solution {
    typedef long long ll;
public:
    int maxFrequency(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end());
        int n=nums.size();
        ll prev[100005]={0};
        for(int i=1;i<=n;++i){
            prev[i]=prev[i-1]+nums[i-1];
        }
        int l=0;
        int ans=0;
        for(int j=1;j<=n;++j){
            ll cnt=(ll)nums[j-1]*(j-l)-prev[j]+prev[l];
            while(cnt>k){
                l++;
                cnt=(ll)nums[j-1]*(j-l)-prev[j]+prev[l];
            }
            ans=max(ans,j-l);
        }
        return ans;
    }
};

2.LeetCode:1590. 使数组和能被 P 整除

原题链接


        给你一个正整数数组 nums,请你移除 最短 子数组(可以为 空),使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。

        请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。

        子数组 定义为原数组中连续的一组元素

        示例 1:

        输入:nums = [3,1,4,2], p = 6

        输出:1

        示例 2:

        输入:nums = [6,3,5,2], p = 9

        输出:2

        示例 3:

        输入:nums = [1,2,3], p = 3

        输出:0

        示例 4:

        输入:nums = [1,2,3], p = 7

        输出:-1

        示例 5:

        输入:nums = [1000000000,1000000000,1000000000], p = 3

        输出:0

        提示:

        1 <= nums.length <= 1e5

        1 <= nums[i] <= 1e9

        1 <= p <= 1e9


        这道题思路很简单,要使得全部元素和能被p整除,只需要在数组中找到一个和为余数且长度最小的子数组即可。假设这段区间为[j,i],那么公式就是:prev[n]-(prev[i]-prev[j])==kp;这个公式中有两个变量,其中一个我们可以进行枚举,这里选择i。

        那么这种情况下,公式就变成了kp-prev[n]+prev[i]==prevp[j],固定i的情况下我们根据公式去寻找prev[j]即可,这里可以利用unordered_map来进行查找。

        这里prev中的所有下标同样是为了求取前缀和方便进行了偏移。

class Solution {
    unordered_map<int,int> map;
public:
    int minSubarray(vector<int>& nums, int p) {
        int n=nums.size();
        int prev[100005]={0};
        for(int i=1;i<=n;++i){
            prev[i]=prev[i-1]+nums[i-1];
            prev[i]%=p;
        }
        if(!prev[n]){
            return 0;
        }
        map[0]=0;//要求不能全部删除,map[0]要置为0
        int ans=0x3f3f3f;
        for(int i=1;i<=n;++i){
            int target=(p-prev[n]+prev[i])%p;
            //都是求取余数,kp和p对结果无影响
            if(map.find(target)!=map.end()){
                ans=min(ans,i-map[target]);
            }//找到就更新距离
            map[prev[i]]=i;
        }
        if(ans==0x3f3f3f||ans==n){
            ans=-1;
        }
        return ans;
    }
};

3.LeetCode:1589. 所有排列中的最大和

原题链接


        有一个整数数组 nums ,和一个查询数组 requests ,其中 requests[i] = [starti, endi] 。第 i 个查询求 nums[starti] + nums[starti + 1] + … + nums[endi - 1] + nums[endi] 的结果 ,starti 和 endi 数组索引都是 从 0 开始 的。

        你可以任意排列 nums 中的数字,请你返回所有查询结果之和的最大值。

        由于答案可能会很大,请你将它对 109 + 7 取余 后返回。

        示例 1:

        输入:nums = [1,2,3,4,5], requests = [[1,3],[0,1]]

        输出:19

        示例 2:

        输入:nums = [1,2,3,4,5,6], requests = [[0,1]]

        输出:11

        示例 3:

        输入:nums = [1,2,3,4,5,10], requests = [[0,2],[1,3],[1,1]]

        输出:47

        提示:

        n == nums.length

        1 <= n <= 1e5

        0 <= nums[i] <= 1e5

        1 <= requests.length <= 1e5

        requests[i].length == 2

        0 <= starti <= endi < n


        这道题就很简单了,利用差分数组求出来每个位置被查询测次数然后排序,原数组也进行排序,对应的位置相乘即可。

#define mod 1000000007
typedef long long ll;
class Solution {
public:
    int maxSumRangeQuery(vector<int>& nums, vector<vector<int>>& requests) {
        int n=nums.size();
        int diff[n+1];
        memset(diff,0,sizeof(diff));
        for(auto & i:requests){
            diff[i[0]]++;
            diff[i[1]+1]--;
        }
        for(int i=1;i<n;++i){
            diff[i]+=diff[i-1];
        }
        sort(diff,diff+n);
        sort(nums.begin(),nums.end());
        ll ans=0;
        for(int i=0;i<n;++i){
            ans+=(ll)nums[i]*diff[i];//这里要强转一下,否则会爆int
            ans%=mod;
        }
        return ans;
    }
};

4.LeetCode:1712. 将数组分成三个子数组的方案数

原题链接


        我们称一个分割整数数组的方案是 好的 ,当它满足:

        数组被分成三个 非空 连续子数组,从左至右分别命名为 left , mid , right 。

        left 中元素和小于等于 mid 中元素和,mid 中元素和小于等于 right 中元素和。

        给你一个 非负 整数数组 nums ,请你返回 好的 分割 nums 方案数目。由于答案可能会很大,请你将结果对 109 + 7 取余后返回。

        示例 1:

        输入:nums = [1,1,1]

        输出:1

        示例 2:

        输入:nums = [1,2,2,2,5,0]

        输出:3

        示例 3:

        输入:nums = [3,2,1]

        输出:0

        提示:

        3 <= nums.length <= 1e5

        0 <= nums[i] <= 1e4


        这道题的话,可以三分,也可以二分,这里就介绍二分的方法吧(因为三分的还没看懂)。

        我们枚举中间数组 右边界i,然后对其他数组进行枚举。先看左边,我们要找到符合条件的最大的左数组右边界的值。这样是符合条件的临界值,当左移这个边界的时候肯定也会符合条件,因为左数组的和在这种情况下只能减小,中间数组的和在这种情况下只能增加。具体查找就是利用前缀和数组了十分简单。(左数组的和小于等于中间数组)

        左数组的最有边界找到之后,我们寻找中间数组的最左边界。这里的范围就是左数组到左数组的最右边界。这时候找到符合条件的最左边界即可(中间数组和小于等于右数组),最后对这两个边界的范围进行累加。

        注意这里并没有进行下标偏移求前缀和,直接利用原数组来求取,所以要注意下标的值。

class Solution {
    const long long mod= 1e9+7;
public:
    int waysToSplit(vector<int>& prev) {
        int n=prev.size();
        for(int i=1;i<n;++i){
            prev[i]+=prev[i-1];
        }
        int ans=0;
        for(int i=1;i<n-1;++i){
            int l=0,r=i-1;
            int lr=-1;
            while(l<=r){
                int mid=l+((r-l)>>1);
                if(prev[mid]<=prev[i]-prev[mid]){
                    lr=mid;
                    l=mid+1;
                }else r=mid-1;
            }
            if(lr==-1){
                continue;
            }
            int rl=-1;
            l=0,r=lr;
            while(l<=r){
                int mid=l+((r-l)>>1);
                if(prev[i]-prev[mid]<=prev[n-1]-prev[i]){
                    rl=mid;
                    r=mid-1;
                }else l=mid+1;
            }
            if(rl!=-1){
                ans+=lr-rl+1;
                ans%=mod;
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值