六月集训打卡Day8 ---【前缀和】

前言

        来自 英雄哪里出来 的一个 免费 集训,每天 5 5 5 点打卡学习算法(我是为了卷吗,主要是想早起 ),希望能坚持下去。这里用来复盘每天都的打卡题目。
       今日份知识点:前缀和
        今天是起飞的一天,摸一天。

一、题目

题目 难度
1838. 最高频元素的频数 ⭐️⭐️
1590. 使数组和能被 P 整除 ⭐️⭐️⭐️⭐️
1589. 所有排列中的最大和 ⭐️
1712. 将数组分成三个子数组的方案数 ⭐️⭐️⭐️

二、算法思路

1、最高频元素的频数

        (1)【枚举】首先排序,将相同的元素放到相邻的位置,方便统计个数,记录每一组相同元素的开始下标以及个数;遍历计数数组,从记录的开始下标前一个依次往前取可能的最大频数,保留最大的频数。
        时间复杂度: O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    int maxFrequency(vector<int>& nums, int k) {
        unordered_map<int, int> cnt;
        sort(nums.begin(), nums.end());
        for (auto i = 0, j = i; i < nums.size() && j < nums.size(); ++ i) {
            while (j < nums.size() && nums[i] == nums[j]) ++ j;
            i = j - 1;
            cnt[i] = j - i;
        }
        int ret = 1;
        for (auto x: cnt) {
            int i = x.first;
            int d = k;
            int ans = x.second;
            for (int j = i - 1; j >= 0; -- j) {
                if (abs(nums[j] - nums[i]) <= d) {
                    ++ ans;
                    d -= abs(nums[j] - nums[i]);
                }else {
                    break;
                }
            }
            ret = max(ret, ans);
        }
        return ret;
    }
};

        (2)【前缀和+二分+滑动窗口】先对原数组进行从小到大排序,如果存在最优解 len,意味着至少存在一个大小为 len 的区间 [l, r],使得在操作次数不超过 k 次的前提下,区间 [l, r] 的任意值为 nums[i] 调整为 nums[r]。二分答案 len 作为窗口长度,利用前缀和我们可以在O(1)的时间内获得任意区间的和,判断区间是否合理的条件为 nums[r] * len <= sum[l, r] + k注意:不开 long long 见祖宗。
        时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

class Solution {
public:
    vector<int> nums;
    vector<long long> sum;
    int n, k;
    int maxFrequency(vector<int>& _nums, int _k) {
        nums = _nums;
        k = _k;
        n = nums.size();
        sort(nums.begin(), nums.end());
        sum.resize(n + 1);
        for (int i = 1; i <= n; ++ i) {
            sum[i] = sum[i - 1] + (long long)nums[i - 1];
        }
        int l = 0, r = n;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (check(mid)) l = mid;
            else r = mid - 1;
        }
        return r;
    }
    bool check(int len) {
        for (int l = 0; l + len - 1 < n; l++) {
            int r = l + len - 1;
            long long cur = sum[r + 1] - sum[l];
            long long t = (long long)nums[r] * len;
            if (t - cur <= k) return true;
        }
        return false;
    }
};

2、使数组和能被 P 整除

        【前缀和+哈希】
        原理:我们需要找到一个最小的区间 s u b sub sub,使得
(sum - sub) % p = 0    ⟹    sum %p = sub % p \text{(sum - sub) \% p = 0} \\ \implies \text{sum \%p = sub \% p} (sum - sub) % p = 0sum %p = sub % p        时间复杂度: O ( n ) O(n) O(n)

class Solution {
public:
    int minSubarray(vector<int> nums, int p) {
        int n = nums.size();
        unordered_map<int, int> mp;
        vector<long long> sum(n + 1);
        for (int i = 1; i <= n; i ++ ) {
            sum[i] = sum[i - 1] + nums[i - 1];
        }
        long long mod = sum.back() % p;
        if (mod == 0) return 0;

        mp[0] = -1;
        int res = n;
        for (int i = 1; i <= n; i ++) {
            long long submod = sum[i] % p;
            long long tarmod = (submod - mod + p) % p;
            if (mp.count(tarmod)) {
                res = min(res, i - mp[tarmod] - 1);
                if (res == 1 && n != 1)
                    return 1;
            }
            mp[submod] = i - 1;
        }
        //cout << res << endl;
        return res == n ? -1 : res;
    }
};

3、所有排列中的最大和

         【 贪 心 + 差 分 + 前 缀 和 】 【贪心+差分+前缀和】 ++时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)
        原理:我们最后需要得到 m m m 次查询的区间和的最大值。假如我们统计每个数组每个下标被查询的次数(某次查询的区间 [ l , r ] [l, r] [l,r],我们认为 [ l , r ] [l,r] [l,r] 的位置都被查询了一次),统计完每个位置的查询次数后,我们令数组中的最大值位于被查询次数最多的位置,以此类推,即可得到查询结果的最大值。

        步骤:
        (1)统计查询次数时,我们可以使用 差分 数组 s s s 记录(对于查询区间 [ l , r ] [l,r] [l,r],我们执行操作 s[l] ++, s[r + 1] --;),可以在 O ( 1 ) O(1) O(1) 的时间里记录次数,之后进行前缀和操作可以在 O ( n ) O(n) O(n) 时间内还原查询的次数。
        (2)分别对 n u m s nums nums s s s 逆序排序, O ( n log ⁡ n ) O(n\log{n}) O(nlogn)
        (3)将两个数组对应下标的元素相乘,最后即答案, O ( n ) O(n) O(n)

class Solution {
public:
    int maxSumRangeQuery(vector<int>& nums, vector<vector<int>>& requests) {
        long long MOD = 1e9 + 7;
        int n = nums.size();
        vector<long long> s(n + 1, 0);
        for (int i = 0; i < requests.size(); i++) {
            s[requests[i][0]] ++;
            s[requests[i][1] + 1]--;
        }
        for (int i = 1; i <= n; i ++) {
            s[i] += s[i - 1];
        }
        sort(s.begin(), s.end() - 1, greater<long long>());
        sort(nums.begin(), nums.end(), greater<int>());
        long long ans = 0;
        for (int i = 0; i < n; i ++) {
            ans += (s[i] * nums[i]);
        }
        return ans % MOD;
    }
};

4、将数组分成三个子数组的方案数

         【 前 缀 和 + 暴 力 】 【前缀和+暴力】 +时间复杂度: O ( n 2 ) O(n^2) O(n2)
        (1)对数组求前缀和数组 s u m sum sum
        (2)遍历中间数组的起点 [ 1 , n − 1 ] [1,n-1] [1,n1]:
        (3)找到一个最小的区间使得 中间的数组和大于等于第一个数组和,记录这时的第二个下标 l l l
        (4)找到一个最大的区间使得 中间的数组和小于等于最后一个的数组和,记录这时的下标 r r r
        (5)当前起点的可切分方案为 r − l + 1 r-l+1 rl+1
         分 析 : \color{red}分析: 最大的数据量为 1 0 5 \color{red}10^5 105,那么计算量为 1 0 10 \color{red}10^{10} 1010,那么肯定会 T L E \color{red}TLE TLE,我们需要 优 化 \color{green}优化


         【 前 缀 和 + 二 分 查 找 】 【前缀和+二分查找】 +时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)
        优化方向:将步骤(3)、(4)使用 二 分 二分 优化,使时间复杂度从 O ( n ) O(n) O(n) 降为 O ( log ⁡ n ) O(\log{n}) O(logn),那么总体的时间复杂度降为 O ( n log ⁡ n ) O(n\log{n}) O(nlogn),计算次数为 1 0 8 \color{green}10^8 108

class Solution {
public:
    int waysToSplit(vector<int>& nums) {
        int n = nums.size();
        long long MOD = 1e9 + 7;
        vector<long long> sum(n + 1);
        for (int i = 1; i <= n; i ++) {
            sum[i] = sum[i - 1] + nums[i - 1];
        }
        long long ans = 0;
        for (int i = 2; i < n; i ++) {
            long long pre = sum[i - 1];
            int l = i, r = n - 1;
            while (l < r) {
                int mid = l + (r - l) / 2;
                if (sum[mid] - sum[i - 1] >= pre) r = mid;
                else l = mid + 1;
            }
            if (sum[r] - pre < pre || sum[n] - sum[r] < sum[r] - pre)
                continue;
            int ll = r, rr = n - 1;
            while (ll < rr) {
                int mid = ll + (rr - ll + 1) / 2;
                if (sum[mid] - pre <= sum[n] - sum[mid]) ll = mid;
                else rr = mid - 1;
            }
            ans += ll - r + 1;
        }
        return ans % MOD;
    }
};

你可能感兴趣的:(六月集训打卡,算法,c++,开发语言)