青训营刷题-贪心的小包题解

85 阅读3分钟

一般做法

1.前缀和数组

您使用了前缀和数组 prefixSum 来计算子数组的和。这是正确的做法,可以简化子数组和的计算。

int[] prefixSum = new int[n + 1];
prefixSum[0] = 0;
for(int i = 1; i <= n; i++){
    prefixSum[i] = prefixSum[i - 1] + data[i - 1];
}

2. 暴力求解

使用了双重循环来遍历所有可能的子数组,并计算其和,更新最大和。

int sum = Integer.MIN_VALUE;
for(int start = 0; start < n; start++){
    for(int end = start; end < n; end++){
        int currentSum = prefixSum[end + 1] - prefixSum[start];
        sum = Math.max(sum, currentSum);
    }
}

问题分析

  1. 时间复杂度:时间复杂度是 O(n^2),这对于大数据量(如 N * M 接近 10^5 或更大)来说效率太低。
  2. 跨周期处理:只处理了一个周期的情况,没有考虑到当 M > 1 时,可能存在跨周期的最大子数组和。

改进方法

  1. Kadane 算法:Kadane 算法可以在 O(n) 时间内找到单周期的最大子数组和。(ps:kadane算法视频:www.bilibili.com/video/BV1DT…
  2. 跨周期处理:处理跨周期的情况需要考虑从最后一个元素到某个中间点的最大前缀和与从第一个元素到某个中间点的最大后缀和之和,再乘以周期数减去1。

思路

  1. Kadane 算法kadane 方法用于计算单周期的最大子数组和。

  2. 单周期处理:如果 M == 1,直接返回单周期的最大子数组和。

  3. 跨周期处理

    • 计算整个数组的总和 totalSum
    • 计算从左到右的最大前缀和 maxPrefixSum 和从右到左的最大后缀和 maxSuffixSum
    • 跨周期的最大子数组和 crossMaxmaxPrefixSum + maxSuffixSum,如果 totalSum > 0,则加上 (M - 2) * totalSum
  4. 最终结果:返回单周期最大子数组和与跨周期最大子数组和中的较大值。

1. 单周期最大子数组和(Kadane算法)

使用 Kadane 算法可以高效地找到单周期的最大子数组和

  • maxSum:记录到目前为止找到的最大子数组和。

  • maxCurrent:记录当前子数组的和。

  • 遍历数组中的每个元素 value

    1. value 加到 maxCurrent 上。
    2. 如果 maxCurrent 大于 maxSum,更新 maxSum
    3. 如果 maxCurrent 小于 0,重置 maxCurrent 为 0(因为负数会降低子数组的和)。
private static int kadane(int[] data) {
    int maxSum = Integer.MIN_VALUE, maxCurrent = 0;
    for (int value : data) {
        maxCurrent += value;
        if (maxSum < maxCurrent) {
            maxSum = maxCurrent;
        }
        if (maxCurrent < 0) {
            maxCurrent = 0;
        }
    }
    return maxSum;
}

2. 跨周期最大子数组和

我们需要计算从左到右的最大前缀和从右到左的最大后缀和,然后考虑跨周期的情况

  • maxPrefixSum:从左到右的最大前缀和。

  • maxSuffixSum:从右到左的最大后缀和。

  • currentPrefixSum:当前前缀和。

  • currentSuffixSum:当前后缀和。

  • 遍历数组中的每个元素:

    1. 更新 currentPrefixSum 并与 maxPrefixSum 比较,取较大值。
    2. 更新 currentSuffixSum 并与 maxSuffixSum 比较,取较大值。
  • crossMax:跨周期的最大子数组和。
  • 初始值为 maxPrefixSum + maxSuffixSum
  • 如果 totalSum > 0,说明整个数组的和是正数,可以重复利用这些正数来增加总和。因此,加上 (M - 2) * totalSum
public static int solution(int N, int M, int[] data) {
    int maxKadane = kadane(data); // 单周期的最大子数组和
    
    if (M == 1) {
        return maxKadane; // 如果只有一轮,直接返回
    }
​
    int totalSum = 0;
    for (int num : data) {
        totalSum += num;
    }
​
    int maxPrefixSum = 0, maxSuffixSum = 0, currentPrefixSum = 0, currentSuffixSum = 0;
    for (int i = 0; i < N; i++) {
        currentPrefixSum += data[i];
        maxPrefixSum = Math.max(maxPrefixSum, currentPrefixSum);
​
        currentSuffixSum += data[N - 1 - i];
        maxSuffixSum = Math.max(maxSuffixSum, currentSuffixSum);
    }
​
    // 跨周期的最大子数组和
    int crossMax = maxPrefixSum + maxSuffixSum;
    if (totalSum > 0) {
        crossMax += (M - 2) * totalSum;
    }
​
    return Math.max(maxKadane, crossMax);
}

完整代码

public class Main {
    public static int solution(int N, int M, int[] data) {
        int maxKadane = kadane(data); // 单周期的最大子数组和
        
        if (M == 1) {
            return maxKadane; // 如果只有一轮,直接返回
        }
​
        int totalSum = 0;
        for (int num : data) {
            totalSum += num;
        }
​
        int maxPrefixSum = 0, maxSuffixSum = 0, currentPrefixSum = 0, currentSuffixSum = 0;
        for (int i = 0; i < N; i++) {
            currentPrefixSum += data[i];
            maxPrefixSum = Math.max(maxPrefixSum, currentPrefixSum);
​
            currentSuffixSum += data[N - 1 - i];
            maxSuffixSum = Math.max(maxSuffixSum, currentSuffixSum);
        }
​
        // 跨周期的最大子数组和
        int crossMax = maxPrefixSum + maxSuffixSum;
        if (totalSum > 0) {
            crossMax += (M - 2) * totalSum;
        }
​
        return Math.max(maxKadane, crossMax);
    }
​
    private static int kadane(int[] data) {
        int maxSum = Integer.MIN_VALUE, maxCurrent = 0;
        for (int value : data) {
            maxCurrent += value;
            if (maxSum < maxCurrent) {
                maxSum = maxCurrent;
            }
            if (maxCurrent < 0) {
                maxCurrent = 0;
            }
        }
        return maxSum;
    }
​
    public static void main(String[] args) {
        System.out.println(solution(5, 1, new int[]{1, 3, -9, 2, 4}) == 6);
        System.out.println(solution(5, 3, new int[]{1, 3, -9, 2, 4}) == 11);
    }
}