栈:447最大区间乘积问题|豆包MarsCode AI刷题

69 阅读4分钟

栈:447最大区间乘积问题|豆包MarsCode AI刷题

问题描述

小R正在处理一个数组序列,他的任务是找出一个区间,使得这个区间的所有数经过以下计算得到的值是最大的:

区间中的最小数 * 区间所有数的和

小R想知道,经过计算后,哪个区间能产生最大的值。你的任务是帮助小R编写一个程序,输出最大计算值。

例如:给定数组序列 [6, 2, 1],可以得到以下区间及其计算值:

  • [6] = 6 * 6 = 36
  • [2] = 2 * 2 = 4
  • [1] = 1 * 1 = 1
  • [6, 2] = 2 * 8 = 16
  • [2, 1] = 1 * 3 = 3
  • [6, 2, 1] = 1 * 9 = 9

根据这些计算,小R可以选定区间 [6],因此输出的最大值为 36


测试样例

样例1:

输入:n = 3,a = [6, 2, 1]
输出:36

样例2:

输入:n = 4,a = [5, 1, 4, 3]
输出:25

样例3:

输入:n = 5,a = [7, 3, 2, 1, 8]
输出:64

解答

1.计算前N项和

2.区间中的最小数 * 区间所有数的和,由此判断,我们要在和尽量小的情况下找到其中的最小值。而和的最小情况则应该在区间数目尽量小的情况下,而区间是连续的,我们循环遍历每一个数,以这个数所在的区间内找寻最小值,在这个区间内,我们既要保证区间内部的元素数目最少的情况,又要在此区间内找到最小的,我们寻找这个数左右两边满足以下两个条件的区间:

条件1:寻找左右两边的最小值

条件2:在找寻最小值的基础上,找到这个离这个数最近的。

实现方法、

单调递增栈:单调递增栈是一种特殊类型的栈数据结构,其特点在于栈内的元素按照严格递增的顺序排列。

  • 寻找左右两边离得最近的最小值

    1. left 和 right 数组

    • left[i] 存储了 a[i] 左边第一个比它小的元素的索引(如果不存在,则为 -1)。
    • right[i] 存储了 a[i] 右边第一个比它小的元素的索引(如果不存在,则为数组长度 n)。
    1. 数组a[]
    • 遍历数组 a,对于每个元素 a[i],以其为最小值的子区间的和为 prefixSum[right[i]] - prefixSum[left[i] + 1]
    • 计算该子区间的最小值乘以和的结果,并与当前最大值 maxResult 进行比较,更新 maxResult
要点

如何使用单调递增栈找到每个元素左边和右边第一个比它小的元素的位置?

eg:vector a = {5, 2, 6, 1};

  1. 初始状态

    • stk = []
    • left = [-1, -1, -1, -1]
    • right = [4, 4, 4, 4]
  2. 处理元素 a[0] = 5

    • stk = [0]
    • left = [-1, -1, -1, -1]
    • right = [4, 4, 4, 4]
  3. 处理元素 a[1] = 2

    • stk.top() = 0 且 a[0] = 5 > a[1] = 2,因此 right[0] = 1stk = []
    • stk.push(1)
    • left = [-1, 0, -1, -1]
    • right = [1, 4, 4, 4]
  4. 处理元素 a[2] = 6

    • stk.top() = 1 且 a[1] = 2 < a[2] = 6,因此 left[2] = 1
    • stk.push(2)
    • left = [-1, 0, 1, -1]
    • right = [1, 4, 4, 4]
  5. 处理元素 a[3] = 1

    • stk.top() = 2 且 a[2] = 6 > a[3] = 1,因此 right[2] = 3stk = [1]
    • stk.top() = 1 且 a[1] = 2 > a[3] = 1,因此 right[1] = 3stk = []
    • stk.push(3)
    • left = [-1, 0, 1, -1]
    • right = [1, 3, 3, 4]

其他实例

单调递增栈常用于解决一类特定的问题,如找到数组中每个元素的下一个更大元素。以下是一个应用实例:

给定一个温度列表,要求找出每天温度对应在几天后会出现比其温度更高的值,如果气温在这之后都不会升高,则在该位置用0代替。这可以通过单调递增栈来解决。

  • 输入:温度列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73]

  • 输出:[1, 1, 4, 2, 1, 1, 0, 0]

  • 解决步骤

    1. 创建一个空栈 stack 和一个与温度列表等长的数组 ans,用于存储结果。
    2. 遍历温度列表中的每个元素。
    3. 对于每个元素,如果栈不为空且当前元素大于栈顶元素对应的温度,说明栈顶元素对应的温度找到了下一个更高的温度,记录间隔时间并弹出栈顶元素。
    4. 将当前元素的索引压入栈中。
    5. 遍历结束后,栈中剩余的元素对应的温度在后续天数中都不会升高,因此在 ans 数组中对应位置填 0。
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>

using namespace std;

int solution(int n, vector<int> a) {
    // 预处理前缀和(前n项和)
    vector<long long> prefixSum(n + 1, 0);
    for (int i = 0; i < n; ++i) {
        prefixSum[i + 1] = prefixSum[i] + a[i];
    }

    // 使用单调递增栈找到每个元素左边和右边第一个比它小的元素的位置
    stack<int> stk;
    vector<int> left(n, -1), right(n, n);
    for (int i = 0; i < n; ++i) {
        while (!stk.empty() && a[stk.top()] > a[i]) {
            right[stk.top()] = i;
            stk.pop();
        }
        if (!stk.empty()) {
            left[i] = stk.top();
        }
        stk.push(i);
    }

    // 计算每个区间 [left[i], right[i] - 1] 的结果,并找出最大值
    long long maxResult = 0;
    for (int i = 0; i < n; ++i) {
        long long minVal = a[i];
        //
        long long sum = prefixSum[right[i]] - prefixSum[left[i] + 1];
        maxResult = max(maxResult, minVal * sum);
    }

    return maxResult;
}

int main() {
    std::cout << (solution(3, {6, 2, 1}) == 36) << std::endl;
    std::cout << (solution(4, {5, 1, 4, 3}) == 25) << std::endl;
    std::cout << (solution(5, {7, 3, 2, 1, 8}) == 64) << std::endl;
}