AI 刷题 102. 贪心的小包 题解 | 豆包MarsCode AI刷题

84 阅读3分钟

问题描述

小包非常喜欢吃甜点,他收到了一次性送来的 N 个甜点,每个甜点都有一个对应的喜爱值。

但是这还不够!小包让小哥连续送来了 M 次相同的 N 个甜点,并将这些甜点首尾相接排成一排。

现在,小包面前有 (N×M) 个排成一排的甜点,小包希望从中选择一段连续的甜点,使得这段甜点的总喜爱值最大化。

注意:尽管小包喜欢甜食,但有些甜点可能不合口味,导致其喜爱值为负数。小包至少要选择一个甜点来满足他对甜点的贪心。

问题思路解析

首先,我们可以将问题看成是一个求解连续子数组和最大化的问题。为了找到最优的解决方案,我们可以采用“前缀和”技术来高效计算子数组的和,并结合一些优化方法提高求解速度。

1. 问题转化

给定N个甜点的喜爱值,并且这些甜点被连续复制了M次。我们需要从这些(N * M)个甜点中选出一段连续子数组,使得这段子数组的总喜爱值最大。

我们可以将这个问题简化为求一个长度为N * M的数组中,任意连续子数组的最大和。通过前缀和数组的引入,原问题可以转化为求解arr[j] - arr[i]的最大值,其中i < j

2. 前缀和的引入

  • 前缀和:我们定义一个前缀和数组arr,其中arr[i]表示从第1个甜点到第i个甜点的累积和。这样,任意一个子数组的和就可以通过arr[j] - arr[i]来表示,表示的是从第i+1个甜点到第j个甜点的和。
  • 具体的计算方法是:arr[i+1] = data[i % N] + arr[i],这里data[i % N]表示对于第i个甜点,如果i大于N,则通过取余i % N来重复使用第i个甜点的喜爱值。

3. 最小前缀和的优化

为了高效计算最大子数组和,我们引入一个辅助数组brr,用于存储当前前缀和数组中最小的前缀和。我们可以通过比较每个新的前缀和arr[i]与之前的最小前缀和brr[i-1]来更新最大子数组和。

  • 对于每一个i,我们计算arr[i] - brr[i-1],这个值表示从brr[i-1]arr[i]这段区间的和。如果这个值大于之前计算的最大值,我们就更新最大值。
  • brr[i] = min(brr[i-1], arr[i]),通过这个更新方式,我们始终能够追踪到当前arr数组中最小的前缀和,进而帮助我们找到当前i为结尾时最大子数组和。

4. 最终的计算

在每一次迭代中,我们都会更新最大子数组和,并且不断追踪最小前缀和。最终,我们返回计算得到的最大值。

5. 时间复杂度

  • 计算前缀和的时间复杂度是O(N * M),因为我们需要遍历所有甜点。
  • 更新最小前缀和的过程也需要O(N * M)的时间。

完整代码

#include <vector>
#include <limits.h>
using namespace std;
int solution(int N, int M, std::vector<int> data) {
    // Edit your code here
	vector<long long> arr(N*M+1);
    vector<long long> brr(N*M+1);
    for(int i=0;i<N*M;i++){
        arr[i+1]=data[i%N]+arr[i];
    }
    for(int i=1;i<=N*M;i++)
    {
        brr[i]=min(brr[i-1],arr[i]);
    }
    long long res=INT_MIN;
    for(int i=1;i<=N*M;i++){
        long long a=arr[i]-brr[i-1];
        res=max(res,a);
    }
    return res;
}

int main() {
    // Add your test cases here
    std::cout << (solution(5, 1, {1, 3, -9, 2, 4}) == 6) << std::endl;
    std::cout << (solution(5, 3, {1, 3, -9, 2, 4}) == 11) << std::endl;
    return 0;
}