2022-6-8[前缀和] 最高频元素的频数,使数组和能被P整除,所有排列中的最大和,将数组分成三个子数组的方案数

1. 最高频元素的频数 [滑动窗口/前缀和]

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

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

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

Example 1

输入:nums = [1,2,4], k = 5
输出:3
解释:对第一个元素执行 3 次递增操作,对第二个元素执 2 次递增操作,此时 nums = [4,4,4] 。
4 是数组中最高频元素,频数是 3 。

Example 2

输入:nums = [1,4,8,13], k = 5
输出:2
解释:存在多种最优解决方案:
- 对第一个元素执行 3 次递增操作,此时 nums = [4,4,8,13] 。4 是数组中最高频元素,频数是 2 。
- 对第二个元素执行 4 次递增操作,此时 nums = [1,8,8,13] 。8 是数组中最高频元素,频数是 2 。
- 对第三个元素执行 5 次递增操作,此时 nums = [1,4,13,13] 。13 是数组中最高频元素,频数是 2 。

代码 1

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

using ull = unsigned long long;

int maxFrequency(vector<int> &nums, int k) {
    sort(nums.begin(), nums.end());
    vector<ull> sum(nums.size() + 1, 0);
    for (int i = 1; i <= nums.size(); ++i) sum[i] = sum[i - 1] + nums[i - 1];
    int result = 0;
    for (int i = 0, j = 1; j <= nums.size(); ++j) {
        while (sum[j] - sum[i] + k < ull(nums[j - 1]) * (j - i)) i++;
        result = max(result, j - i);
    }
    return result;
}

代码 2

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),对nums排序时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),其余遍历时间复杂度均为 O ( n ) O(n) O(n)代码2与代码1相同,在原数组中相同元素较多的情况下可以提高效率

using uint = unsigned int;

int maxFrequency(const vector<int> &numsInput, int k) {
    unordered_map<uint, uint> mp;
    mp[0] = 0;
    for (int n:numsInput) ++mp[n];
    vector<pair<uint, uint>> nums(mp.begin(), mp.end());
    sort(nums.begin(), nums.end(), [](const pair<uint, uint> &lo, const pair<uint, uint> &hi) { return lo.first < hi.first; });
    vector<uint> prevSum(mp.size()), prevCnt(mp.size());
    for (uint i = 0, sum = 0, cnt = 0; i < nums.size(); ++i) {
        sum += nums[i].first * nums[i].second;
        prevSum[i] = sum;
        cnt += nums[i].second;
        prevCnt[i] = cnt;
    }
    uint iFast = 0, iSlow = 0;
    while (iFast < nums.size() && prevSum[iFast] + k >= nums[iFast].first * prevCnt[iFast]) ++iFast;
    uint result = prevCnt[--iFast];
    while (++iFast < nums.size()) {
        while (prevSum[iFast] - prevSum[iSlow] + k < (prevCnt[iFast] - prevCnt[iSlow]) * nums[iFast].first) ++iSlow;
        uint frequency = (prevCnt[iFast] - prevCnt[iSlow]) + ((prevSum[iFast] - prevSum[iSlow] + k) - (prevCnt[iFast] - prevCnt[iSlow]) * nums[iFast].first) / (nums[iFast].first - nums[iSlow].first);
        result = max(result, frequency);
    }
    return result;
}

代码 3 [官方题解]

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

class Solution {
public:
    int maxFrequency(vector<int> &nums, int k) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        long long total = 0;
        int l = 0, res = 1;
        for (int r = 1; r < n; ++r) {
            total += (long long) (nums[r] - nums[r - 1]) * (r - l);
            while (total > k) {
                total -= nums[r] - nums[l];
                ++l;
            }
            res = max(res, r - l + 1);
        }
        return res;
    }
};

2. 使数组和能被 P 整除 [前缀和]

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

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

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

Example 1

输入:nums = [3,1,4,2], p = 6
输出:1
解释:nums 中元素和为 10,不能被 p 整除。我们可以移除子数组 [4] ,剩余元素的和为 6 。

Example 2

输入:nums = [6,3,5,2], p = 9
输出:2
解释:我们无法移除任何一个元素使得和被 9 整除,最优方案是移除子数组 [5,2] ,剩余元素为 [6,3],和为 9 。

代码

int minSubarray(const vector<int> &nums, int p) {
    vector<int> sum(nums.size() + 1, 0);
    for (int i = 1; i <= nums.size(); ++i) sum[i] = (sum[i - 1] + nums[i - 1]) % p;
    if (sum.back() == 0) return 0;
    unordered_map<int, int> mp;
    int result = INT_MAX;
    for (int i = 0, target; i <= nums.size(); ++i) {
        target = (sum[i] + p - sum.back()) % p;
        if (mp.count(target)) result = min(result, i - mp[target]);
        mp[sum[i]] = i;
    }
    return result == nums.size() ? -1 : result;
}

3. 所有排列中的最大和 [差分数组]

We have an array of integers, nums, and an array of requests where requests[i] = [starti, endi]. The ith request asks for the sum of nums[starti] + nums[starti + 1] + ... + nums[endi - 1] + nums[endi]. Both starti and endi are 0-indexed.

Return the maximum total sum of all requests among all permutations of nums.

Since the answer may be too large, return it modulo 10^9 + 7.

Example 1

Input: nums = [1,2,3,4,5], requests = [[1,3],[0,1]]
Output: 19
Explanation: One permutation of nums is [2,1,3,4,5] with the following result: 
requests[0] -> nums[1] + nums[2] + nums[3] = 1 + 3 + 4 = 8
requests[1] -> nums[0] + nums[1] = 2 + 1 = 3
Total sum: 8 + 3 = 11.
A permutation with a higher total sum is [3,5,4,2,1] with the following result:
requests[0] -> nums[1] + nums[2] + nums[3] = 5 + 4 + 2 = 11
requests[1] -> nums[0] + nums[1] = 3 + 5  = 8
Total sum: 11 + 8 = 19, which is the best that you can do.

Example 2

Input: nums = [1,2,3,4,5,6], requests = [[0,1]]
Output: 11
Explanation: A permutation with the max total sum is [6,5,4,3,2,1] with request sums [11].

代码

constexpr int mod = 1000000007;
using ull = unsigned long long;

int maxSumRangeQuery(vector<int> &nums, vector<vector<int>> &requests) {
    int n = nums.size();
    vector<int> diff(n + 1);  // 差分数组
    for (auto &interval :requests) {
        int l = interval[0], r = interval[1];
        if (l > n) continue;  // 无意义区间
        if (r > n) r = n;  // 删除无意义无间
        ++diff[l], --diff[r + 1];
    }
    vector<tuple<ull, int>> intervalCount;  // count, length
    intervalCount.reserve(nums.size());
    int prev = 0, idx = 0, sum = diff[0];
    while (++idx < diff.size()) {
        if (diff[idx] != 0) {
            intervalCount.emplace_back(sum, idx - prev);
            prev = idx;
            sum += diff[idx];
        }
    }
    sort(intervalCount.begin(), intervalCount.end(), greater<>());
    sort(nums.begin(), nums.end(), greater<>());
    idx = 0;
    ull total = 0;
    for (auto[count, length]:intervalCount) {
        while (length--) {
            total += count * nums[idx++];
            total %= mod;
        }
    }
    return total;
}

4. 将数组分成三个子数组的方案数 [前缀和]

A split of an integer array is good if:

  • The array is split into three non-empty contiguous subarrays - named left, mid, right respectively from left to right.
  • The sum of the elements in left is less than or equal to the sum of the elements in mid, and the sum of the elements in mid is less than or equal to the sum of the elements in right.

Given nums, an array of non-negative integers, return the number of good ways to split nums. As the number may be too large, return it modulo 10^9 + 7.

Example 1

Input: nums = [1,1,1]
Output: 1
Explanation: The only good way to split nums is [1] [1] [1].

Example 2

Input: nums = [1,2,2,2,5,0]
Output: 3
Explanation: There are three good ways of splitting nums:
[1] [2] [2,2,5,0]
[1] [2,2] [2,5,0]
[1,2] [2,2] [5,0]

代码 [前缀和]

int waysToSplit(const vector<int> &nums) {
    int n = nums.size();
    vector<int> sum(n, 0);
    for (int i = 0, s = 0; i < n; ++i) sum[i] = s = s + nums[i];
    int result = 0;
    for (int i = 0, left, mid, right; i < n - 2; ++i) {
        left = sum[i];
        if (left > sum.back() / 3) break;  // 剪枝
        for (int j = i + 1; j < n - 1; ++j) {
            mid = sum[j] - sum[i];
            right = sum[n - 1] - sum[j];
            if (mid > right) break;  // 剪枝
            if (left <= mid && mid <= right) ++result;
        }
    }
    return result;
}

代码 [前缀和/二分]

int waysToSplit(const vector<int> &nums) {
    int n = nums.size(), ans = 0, mod = 1e9 + 7;
    vector<int> sum(n + 1);
    for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];

    auto search_left = [&](int lo, int hi, int left) -> int {
        while (lo < hi) {
            int mi = lo + ((hi - lo) >> 1);
            sum[mi] - left < left ? lo = mi + 1 : hi = mi;
        }
        return lo;
    };

    auto search_right = [&](int lo, int hi, int left) -> int {
        while (lo < hi) {
            int mi = lo + ((hi - lo) >> 1);
            sum[mi] - left <= sum[n] - sum[mi] ? lo = mi + 1 : hi = mi;
        }
        return lo;
    };

    for (int i = 1, l, r; i <= n - 2; i++) {
        l = search_left(i + 1, n, sum[i]);
        r = search_right(l, n, sum[i]);
        ans = (ans + r - l) % mod;
    }

    return ans;
}

代码 [zhangbw]

int waysToSplit(vector<int> &nums) {
    int n = nums.size(), ans = 0, mod = 1e9 + 7;
    vector<int> preSum(n + 1); // 存放前缀和
    for (int i = 1; i <= n; i++) preSum[i] = preSum[i - 1] + nums[i - 1];
    for (int i = 1, l = 0, r = 0; i <= n - 2; i++) { // i为left测的元素个数
        l = max(i + 1, l);
        while (l < n && preSum[l] - preSum[i] < preSum[i]) l++;
        r = max(l, r);
        while (r < n && preSum[r] - preSum[i] <= preSum[n] - preSum[r]) r++;
        ans = (ans + r - l) % mod;
    }
    return ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值