题目描述:
给定一个只包含正数的数组 arr,arr中任何一个子数组 sub,计算一个值M = sub累加和 * sub中的最小值,那么所有子数组中,这个值最大是多少?
way1:
暴力的得到数组 arr 的所有子数组(连续),计算每个子数组的 M 并取max, 时间复杂度O(n^3)。
#include<iostream>
#include<vector>
using namespace std;
int subMax(vector<int>arr)
{
int N = arr.size();
int maxM = INT_MIN;
for (int i = 0; i < N; i++)
{
for (int j = i; j < N; j++)
{
int minNum = INT_MAX;
int sum = 0;
for (int k = i; k <= j; k++)
{
sum += arr[k];
minNum = min(minNum, arr[k]);
}
maxM = max(maxM, sum * minNum);
}
}
return maxM;
}
way2:
对于数组 arr 中的所有值,假设每个值都能当一次它所在的子数组中的最小值,那么要让 M 尽可能的大,就需要子数组sub的累加和尽可能的大了,所以对于当前作为最小值的元素,找到它的左边和右边离它最近且比它小于等于的元素的下标,在这2个下标中间的元素就是以当前作为最小值的元素时,sub的累加和最大的了,计算得到M,对 M 取 max。利用前缀和得到某个区间内子数组的累加和。求arr中每个元素左右两边离它最近且比它小于等于的值时,arr中每个元素(下标)最多入栈出栈一次,然后累加和也是通过前缀和相减得到,所以时间复杂度为O(n)。
如果 arr 是 3 6 6 6, 那么
3 6 6 (第一个6的左边是3,右边是第2个6),
3 6 6(第2个6左边是3,右边是第3个6),
3 6 -1 (第3个6左边是3,右边没有),
-1 3 -1 (3的左边和右边都没有比它小于等于的值)
所以是,有重复值时(比如这里的6,以当前值为最小值,我前面的6都做过最小值了,所以不算,所以其实最后一个6计算出来的M值是最大的,因为子数组是 6 6 6, 第一个6以自己做最小值的子数组是 6,第2个6以自己做最小值的子数组是 6,6。
所以 小于等于就让栈顶元素弹出的话,最后一个相邻重复值得到的区间是最大的,而 如果是前面的 小于让弹出下标列表(单调栈基础题-CSDN博客)的做法的话,是 所有 6 的左边都是 3,所有 6 的右边都是 -1。
int subMax2(vector<int> arr)
{
int N = arr.size();
vector<int>sum(N);
sum[0] = arr[0];
//计算前缀和
for (int i = 1; i < N; i++)
{
sum[i] = sum[i - 1] + arr[i];
}
int maxM = INT_MIN;
stack<int>st;
for (int i = 0; i < N; i++)
{
//假设arr[topIndex]是当前子数组中的最小值
while (!st.empty() && arr[i] <= arr[st.top()])
{
int topIndex = st.top();
st.pop();
int leftIndex = st.empty() ? -1 : st.top();
int rightIndex = i;
int subSum = sum[rightIndex - 1] - (leftIndex >= 0 ? sum[leftIndex] : 0);
maxM = max(maxM, subSum * arr[topIndex]);
}
st.push(i);
}
while (!st.empty())
{
int topIndex = st.top();
st.pop();
int leftIndex = st.empty() ? -1 : st.top();
int rightIndex = N;
int subSum = sum[rightIndex - 1] - (leftIndex >= 0 ? sum[leftIndex] : 0);
maxM = max(maxM, subSum * arr[topIndex]);
}
return maxM;
}