LeetCode1856.子数组最小乘积的最大值 Maximum Subarray Min-Product(Java)
Posted 爱若信若盼若
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode1856.子数组最小乘积的最大值 Maximum Subarray Min-Product(Java)相关的知识,希望对你有一定的参考价值。
LeetCode1856.子数组最小乘积的最大值 Maximum Subarray Min-Product(Java)
##Binary Search##, ##Dynamic Programming##, ##Sort##, ##Union Find##, ##Queue##, ##Dequeue##
最小乘积定义为这个数组中最小值乘以数组的和,题目求数组中非空子数组的最小乘积
则该最小乘积由非空子数组中最小值、和子数组的和决定
单调栈+前缀和
参考LeetCode84
遍历数组中的所有元素,假定当前元素nums[i]
是子数组中的最小值,求该子数组的最小乘积变为nums[i] * sumOfSubArray
,即只需求该子数组的和
问题转变为如何确定子数组即该子数组的和
因为假定当前元素nums[i]
是子数组中的最小值,则在该子数组,当前元素的左边都应该是大于等于nums[i]
的元素,右边同理
因此左边界应当为左边元素中小于nums[i]
且距离nums[i]
最近的元素,有边界同理为右边元素中小于nums[i]
且距离nums[i]
最近的元素
要求上述最近最小/最大元素,应当采用单调栈的数据结构,可以采用单栈或双栈,这里采用双栈(即调用栈求左边最近最小元素,清空栈,再调用栈求右边最近最小元素),双栈更容易表达
这里栈用队列实现,队列用数组实现
确定子数组边界后,要求子数组的和,应当用前缀和压缩时间复杂度
因为数组中元素都大于0,因此在数组两边添加0元素辅助处理,而减少判断边界逻辑
时间复杂度: O ( n ) O(n) O(n)
class Solution {
public int maxSumMinProduct(int[] nums) {
final int N = 100010;
int len = nums.length;
// left记录左边最近小元素,right记录右边最近小元素
int[] newNums = new int[N], left = new int[N], right = new int[N];
// 前缀和用long存储,否则越界
long[] sum = new long[N];
for (int i = 1; i <= nums.length; i ++) { // 初始化newNums,添加边界,初始化前缀和数组
newNums[i] = nums[i - 1];
sum[i] = sum[i - 1] + newNums[i];
}
newNums[0] = newNums[len + 1] = 0;
// tt为数组实现队列的下标
int tt = 0;
int[] queue = new int[N]; // queue[0] = [0]
for (int i = 1; i <= len; i ++) {
while (newNums[i] <= newNums[queue[tt]]) { // 找到左边小于newNums[i]的最近元素
tt --;
}
left[i] = queue[tt];
queue[++ tt] = i;
}
tt = 0; // 重置下标
queue[0] = len + 1; // 右往左遍历
for (int i = len; i >= 1; i --) {
while (newNums[i] <= newNums[queue[tt]]) { // 找到右边小于newNums[i]的最近元素
tt --;
}
right[i] = queue[tt];
queue[++ tt] = i;
}
long max = 0L;
for (int i = 1; i <= len; i ++) {
Long cal = 0L;
cal = (long)newNums[i] * (sum[right[i] - 1] - sum[left[i]]); // 注意right[i] - 1,right[i]表示比nums[i]小离i最近的右边元素,这里考虑的最小元素应该是nums[i],因此right[i] - 1
max = Math.max(max, cal);
}
int res = (int)(max % 1000000007);
return res;
}
}
排序+并查集
设计Pair
存放(nums[i], i)
,表示数组元素及其位置关系
对于nums
中每个num
,都用Pair
表示,且加入ArrayList<Pair> array
中,并对该array
进行排序,排序的方法是按照nums
中元素大小排序
维护一个并查集,并查集联通以该元素为最小值的子数组;并查集中维护int[] sum
,表示子数组和的大小;维护一个boolean[] flag
表示该元素是否被遍历过
按照nums元素从大到小关系对array
进行遍历,每次的当前元素都能保证是未遍历的元素中的最大元素,因为array
已排序,遍历顺序从大到小
判断当前元素pair
是否能和左右联通起来,如果flag[pair.pos - 1]
为true,证明左边元素比该元素大,因为遍历顺序从大到小,若左边元素已被遍历,则左边元素一定比该元素大,则可以进行合并操作
右边元素同样处理
每次遍历过程中,将当前元素是否被遍历过的标志位flag[i]
置为true
,更新答案
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
class Solution {
// 初始化
final int N = 100010;
long[] sum = new long[N];
boolean[] flag = new boolean[N];
int[] parent = new int[N];
public int find(int x) {
if (x == parent[x]) return parent[x];
else return find(parent[x]);
}
// 并查集合并操作需要维护子数组和的大小
public void merge(int x, int y) {
int a = find(x), b =find(y);
sum[a] += sum[b];
parent[b] = a;
}
public int maxSumMinProduct(int[] nums) {
int n = nums.length;
ArrayList<Pair> array = new ArrayList<Pair>();
for (int i = 0; i < n; i ++) { // 初始化
array.add(new Pair(nums[i], i));
parent[i] = i;
flag[i] = false;
sum[i] = (long)nums[i];
}
// 排序,根据Pair中元素大小排序
Collections.sort(array, new Comparator<Pair>() {
public int compare (Pair p1, Pair p2) {
return p2.num - p1.num;
}
});
long ans = 0;
for (int i = 0; i < n; i ++) {
Pair pair = array.get(i);
int position = pair.pos;
if (position < n - 1 && flag[position + 1]) { // 判断右边元素是否越界,是否比当前元素大(是否已经遍历过)
merge(position, position + 1); // 符合条件,并查集合并操作
}
if (position > 0 && flag[position - 1]) { // // 判断左边元素是否越界,是否比当前元素大(是否已经遍历过)
merge(position, position - 1);
}
flag[position] = true; // 标志位记为已经遍历过
int a = find(position);
ans = Math.max(ans, (long)pair.num * sum[a]);
}
ans %= 1000000007;
return (int)ans;
}
}
// 定义Pair
class Pair {
int num;
int pos;
public Pair(int num, int pos) {
this.num = num;
this.pos = pos;
}
}
以上是关于LeetCode1856.子数组最小乘积的最大值 Maximum Subarray Min-Product(Java)的主要内容,如果未能解决你的问题,请参考以下文章