题目来源:https://leetcode.cn/problems/maximum-subarray-min-product/
大致题意:
给一个只包含正数的数组,求所有子数组的最小值与子数组和乘积的最大值
思路
根据题意,首先需要求子数组的最小值与子数组和乘积,才能在所有乘积中找到最大值
要想求出子数组的最小值与子数组和乘积,需要先确定子数组的最小值
而要确定子数组的最小值,需要先确定子数组的内容,简单的方法是直接枚举所有子数组,然后确定最小值,但是这样的时间复杂度为 O(n2)。还有一种逆向思维的方法
- 即确定以数组每个元素为最小值的子数组左右边界 l 和 r,可以通过单调栈求出边界
- 也就是确定以 nums[i] 为最小值的子数组左右边界,那么可知,在边界内包含 nums[i] 的所有子数组的最小值都为 nums[i],而这些子数组与 nums[i] 的最大乘积即为 l ~ r 的所有元素和与 nums[i] 的乘积(因为数组所有元素都为正数)
于是就确定了子数组的最小值与子数组和乘积,然后可以通过枚举每个元素作为最小值的子数组与对应子数组和的乘积求出所有子数组的最小值与子数组和乘积的最大值
在解题时,可以通过前缀和来确定给定范围的子数组和
具体的解题思路为
- 使用单调栈求出每个元素作为最小值的子数组的左右边界
- 统计前缀和
- 根据左边界和前缀和获取对应的子数组和,子数组和与当前元素的乘积即为当前元素为最小值的子数组的最小值与子数组和乘积的最大值。于是,统计最大值(此最大值表示当前元素作为最小值的所有子数组中子数组的最小值与子数组和乘积的最大值)中的最大值(该最大值即为所有子数组的最小值与子数组和乘积的最大值),即为答案
代码:
public int maxSumMinProduct(int[] nums) {
int n = nums.length;
// 每个元素作为最小值的子数组左边界
int[] left = new int[n];
// 每个元素作为最小值的子数组右边界
int[] right = new int[n];
// 单调栈
Deque<Integer> stack = new ArrayDeque<>();
// 获取左边界
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && nums[stack.peek()] >= nums[i]) {
stack.pop();
}
left[i] = stack.isEmpty() ? -1 : stack.peek();
stack.push(i);
}
stack.clear();
// 获取右边界
for (int i = n - 1; i >= 0; i--) {
while (!stack.isEmpty() && nums[stack.peek()] > nums[i]) {
stack.pop();
}
right[i] = stack.isEmpty() ? n : stack.peek();
stack.push(i);
}
long ans = 0;
// 前缀和
long[] preSum = new long[n];
preSum[0] = nums[0];
// 统计前缀和
for (int i = 1; i < n; i++) {
preSum[i] = preSum[i - 1] + nums[i];
}
// 统计最大值
for (int i = 0; i < n; i++) {
// cur 即为当前元素作为最小值的子数组和的最大值
long cur = preSum[right[i] - 1];
if (left[i] != -1) {
cur -= preSum[left[i]];
}
// cur * nums[i] 即为 当前元素为最小值 的 【子数组的最小值与子数组和乘积】 的 最大值
ans = Math.max(cur * nums[i], ans);
}
ans %= 1000000007;
return (int) ans;
}