题目来源
题目描述
- 给定一个只包含正数的数组arr
- arr中任何一个子数组sub,一定都可以算出(sub累加和 )* (sub中的最小值)是什么,
- 那么所有子数组中,这个值最大是多少?
题目解析
题意
子数组是连续的。每一个子数组的累加和都可以算出来,这段数组的最小值也能求得,然后 A指标 = (sub累加和 )* (sub中的最小值)。
现在我们要求出当前数组中哪个子数组的A指标最大。
单调栈
思考
问题:
- 以0位置的
3
3
3作为最小值的子数组有哪些?
- [ 3 ] : 3 ∗ 3 = 9 [3]:3*3 = 9 [3]:3∗3=9
- [ 3 , 4 ] : 3 ∗ 7 = 21 [3,4]:3*7=21 [3,4]:3∗7=21,选它
- 以
1
−
−
>
4
1-->4
1−−>4作为最小值的子数组有哪些?
- 4 : 4 ∗ 4 = 16 4:4*4=16 4:4∗4=16
- 以
2
−
−
>
2
2-->2
2−−>2作为最小值的子数组有哪些?
- 得到:[3,4,2,3,4,6]$
- 2尽量往左扩大,最多能扩到 [ 3 , 4 , 2 [3,4,2 [3,4,2
- 2尽量往右扩张,最大能扩到…2,3,4,6]
- 为什么要尽量扩张,因为 指标 A = s u m ∗ n u m 指标A = sum * num 指标A=sum∗num,指标A要尽量大,而num是固定的,所以sum应该尽量大
- [ 3 , 4 , 2 , 3 , 4 , 6 ] [3,4,2,3,4,6] [3,4,2,3,4,6]含有 2 2 2的所有子数组的指标A都会比2小
- 得到:[3,4,2,3,4,6]$
- 以
3
−
−
>
3
3-->3
3−−>3作为最小值的子数组有哪些?
- 。。。
- 以
4
−
−
>
4
4-->4
4−−>4作为最小值的子数组有哪些?
- 。。。
- 以
5
−
−
>
6
5-->6
5−−>6作为最小值的子数组有哪些?
- 。。。
第一个要解决的问题:
- 如何找出以
x
x
x位置作为最小值的子数组的指标A最大呢?
- x尽量往左看,找到比x更小的那个索引,假设为left
- x尽量往右看,找到比x更小的那个索引,假设为right
- 然后[left+1…right+1]这个区间范围内的所有数组全要,得到sum,然后sum * 2
第二个要解决的问题:求sum
- 用前缀和
注意,这里,单调栈不需要用链表。举个例子
(1)遍历数组,并入栈
- 1—>4出栈,需要结算答案,得到
- 1—>4:0—>3,2—>3
- 也就是对于1—>4来说,它左扩不到0—>3位置,右扩不到2—>3,因此它只有它自己,{4},得到4*4 = 16
那现在2—>3能入栈吗?不能,因为栈顶元素也是3,所以0—>3出栈:
- 0—>3出栈
- 左边比我小的数:没有
- 右边比我小的数:如果按照原来单调性的性质,应该是2—>3,但是实际上不对,应该是4—>3。怎么处理呢?
- 无需处理,错了就错了,因为 0—>3和2—>3、4—>3是联通的,有朝一日会自动纠正的
- 也就是说得到了子数组{3,4},得到了7*3=21
现在栈变为:
继续遍历:
-
3—>4出栈:
- 左边比 3—>4小的数:2—>3
- 右边比 3—>4小的数:4—>3
- 所以 3—>4向左扩到2—>3(不包括)为止,向右扩到4—>3(不包括)为止。得到子数组[4],最终得到指标A=4*4=16
-
2–>3也需要出栈:
- 左边比2–>3小的数:无
- 右边比2–>3小的数:4—>3
继续遍历:5—>1可以入栈
5—>1可以入栈吗?不可以,为了满足栈单调增长,必须将4—>3出栈, 5—>1才能入栈
- 4—>3出栈
- 右边比 4—>3小的数:5—>1,往右扩展到索引5(不包括)位置
- 左边比4—>3小的数:没有,所以4—>3可以一直往左扩大,将索引4左边的所有数都放入到当前子数组中
- 最终得到的子数组:[3、4、3、4、3]
小结:反正最后一个能算对,所以之前错了就错了,no care
实现
class Solution {
int maxSumMinProduct(vector<int>& arr){
int size = arr.size();
std::vector<int> sum(size);
sum[0] = arr[0];
for (int i = 1; i < size; ++i) {
sum[i] = sum[i - 1] + arr[i];
}
int max = INT32_MIN;
std::stack<int> stack;
for (int i = 0; i < size; ++i) {
while (!stack.empty() && arr[stack.top()] >= arr[i]){
int j = stack.top(); stack.pop();
int subSum = 0;
if(stack.empty()){
subSum = sum[i - 1];
}else{
subSum = sum[i - 1] - sum[stack.top()];
}
max = std::max(max, subSum * arr[j]);
}
stack.push(i);
}
while (!stack.empty()){
int j = stack.top(); stack.pop();
int subSum = 0;
if(stack.empty()){
subSum = sum[size - 1];
}else{
subSum = sum[size - 1] - sum[stack.top()];
}
max = std::max(max, subSum * arr[j]);
}
return max;
}
};
- 注意测试题目数量大,要取模
- 注意溢出的处理即可,也就是用long类型来表示累加和
- 还有优化就是,你可以用自己手写的数组栈,来替代系统实现的栈,也会快很多
class Solution {
int maxSumMinProduct(vector<int>& arr){
int size = arr.size();
std::vector<long> sum(size);
sum[0] = arr[0];
for (int i = 1; i < size; ++i) {
sum[i] = sum[i - 1] + arr[i];
}
long max = LONG_MIN;
std::vector<int> stack(size);
int stackSize = 0;
for (int i = 0; i < size; ++i) {
while (stackSize != 0 && arr[stack[stackSize - 1]] >= arr[i]){
int j = stack[--stackSize];
long subSum = 0;
if(stack.empty()){
subSum = sum[i - 1];
}else{
subSum = sum[i - 1] - sum[stack[stackSize - 1]];
}
max = std::max(max, subSum * arr[j]);
}
stack[stackSize++] = i;
}
while (stackSize != 0 && arr[stack[stackSize - 1]] >= arr[i]){
int j = stack[--stackSize];
long subSum = 0;
if(stack.empty()){
subSum = sum[stackSize - 1];
}else{
subSum = sum[stackSize - 1] - sum[stack[stackSize - 1]];
}
max = std::max(max, subSum * arr[j]);
}
return (int) (max % 1000000007);
}
};
小结
- [最小乘积]定义为[最小值] * [和],由于[和]比较难以枚举,我们可以考虑枚举[最小值]
- 我们可以枚举数组中的每个元素 n u m s i nums_i numsi作为最小值。由于数组中的元素均为正数,那么我们选择的包含 n u m s i nums_i numsi的子数组应该越长越好
- 应该怎么选择子数组?
- 我们选择子数组的限制只有一点,那就是
n
u
m
s
i
nums_i
numsi必须是子数组中的最小值]。那么我们应该找到:
- 在 n u m s i nums_i numsi[之前]而且严格小于 n u m s i nums_i numsi的元素,并且它离 n u m s i nums_i numsi最近,该元素的下标记为 l e f t i left_i lefti
- 在 n u m s i nums_i numsi[之后]而且严格小于 n u m s i nums_i numsi的元素,并且它离 n u m s i nums_i numsi最近,该元素的下标记为 r i g h t i right_i righti
- 如果不存在这样的元素,那么对应的 l e f t i = − 1 left_i = -1 lefti=−1或者 r i g h t i = n right_i = n righti=n,其中 n n n是数组 n u m s nums nums的长度
- 此时,闭区间 [ l e f t i + 1 , r i g h t i − 1 ] [left_i+1,right_i-1] [lefti+1,righti−1]即为包含 n u m s i nums_i numsi作为最小值而且最长的子数组
- 怎么找这样的区间呢?可以使用[单调栈]
- 我们选择子数组的限制只有一点,那就是
n
u
m
s
i
nums_i
numsi必须是子数组中的最小值]。那么我们应该找到:
暴力
class Solution {
int maxSumMinProduct(vector<int>& arr){
int max = INT32_MIN;
for (int i = 0; i < arr.size(); ++i) {
for (int j = i; j < arr.size(); ++j) {
int minNum = INT32_MAX;
int sum = 0;
for (int k = i; k < j; ++k) {
sum += arr[k];
minNum = std::min(minNum, arr[k]);
}
max = std::max(max, minNum * sum);
}
}
return max;
}
};