leetcode 1856. 子数组最小乘积的最大值

一个数组的 最小乘积 定义为这个数组中 最小值 乘以 数组的

  1. 「最小乘积」的定义为「最小值」乘以「和」,由于「和」较难进行枚举,我们可以考虑枚举「最小值」。
  2. 我们可以枚举数组中的每个元素 nums 作为最小值

由于数组中的元素均为正数,那么我们选择的包含 nums 的子数组是越长越好的。

我们选择子数组的限制 nums 必须是子数组中的最小值」。那么我们应当找到

  1. nums 之前 距离 nums最近的且小于nums的元素 标记为 left
  2. nums 之后 距离 nums 最近的且小于nums的元素 标记为 right

如果没有上述说法中的最小值的这样的元素,就是说左边没有最小值 例如 nums=1 [1,2,3] 那么1 的left 就记为 -1 如果右边也没有最小值 例如 3 和 1 右边都没有最小值 那么就记为 right = n

此时 闭区间[left + 1 ,right - 1] 就是nums作为最小值 且 最长的子数组

可以使用单调栈来计算每个nums 的left 和right 值

有了这个子数组 再使用前缀和 来计算这个闭区间的和 是O(1) 的

生成前缀和是O(N) 获取每个nums的left和right 也是O(N) 的 那么 此题目时间复杂度就在 O(N)

生成的前缀和和数组长度一致,最坏情况下单调栈可能存储N个元素 所以空间复杂度为 O(N)

例: [1,2,3] 单调栈会全部存储进去 最后弹出时候计算 left和right

本题细节: 如果数组内无重复值 构建的单调栈存储的是元素的索引,如果有重复值 里面存放的是链表。

但是本题数组有重复值 但是我们采用的是数组内无重复值 这种方法,其中left是严格定义必须是nums之前距离nums最近且小于nums的元素,但是right 定义为nums之后 距离nums最近且小于等于 nums 的元素这样子对正确的答案并不会造成影响。在严格遵守定义的条件下,答案对应的子数组中,每一个最小的元素都对应着正确的答案,但是 在right 不遵守定义条件下,答案对应的数组中,只有最后出现的最小元素对应正确答案,我们要求的是最大值,所以保证只要有一个最大值即可。

举例: [7,8,3,4,3,2,5] 其中有两个3 第一个3的区间计算出来的是 [0,3] 第二个3 的区间计算的是[0,4] 只有最后出现的那个相同的数字才是正确的答案。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^7

请注意,最小乘积的最大值考虑的是取余操作 之前 的结果。题目保证最小乘积的最大值在 不取余 的情况下可以用 64 位有符号整数 保存。(那这么说 使用long类型 最后取余即可)

前缀和注意溢出问题,以及最后对结果取模

    public static int maxSumMinProduct(int[] nums) {
        // 1. 寻找一个子数组的和 使用前缀和数组
        if (nums == null || nums.length == 0) {
            return -1;
        }
        long[] prefixSum = new long[nums.length];
        prefixSum[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            prefixSum[i] = prefixSum[i - 1] + nums[i];
        }
        // 例:  [2,3,5,1,7,6,4]      nums
        //       0 1 2 3 4 5 6
        // 前缀和[2,5,10,11,18,24,28] prefixSum
        //       0 1  2 3  4  5  6
        // 想要获取 索引 2~4 的子数组的和 那么0~4的和是18 使用 0~4的和 减去 0~1的和 就是 子数组2~4的和 18 - 5 = 13
        // 5 + 1 + 7 也是等于 13

        // 使用单调栈 以数组中每个元素当做最小值的话 可以找到每个元素 子数组的左边界和右边界
        // 通过前缀和数组算出 这个子数组的sum  * min 就是最少乘积 获取最大的那个乘积
        Stack<Integer> stack = new Stack<>();
        long max = Long.MIN_VALUE;
        for (int i = 0; i < nums.length; i++) {
            while (!stack.isEmpty() && nums[stack.peek()] >= nums[i]) {
                Integer minIndex = stack.pop();
                max = Math.max(max, (stack.isEmpty() ? prefixSum[i - 1] : prefixSum[i - 1] - prefixSum[stack.peek()]) * nums[minIndex]);
            }
            stack.push(i);
        }
        while (!stack.isEmpty()) {
            Integer minIndex = stack.pop();
            max = Math.max(max, (stack.isEmpty() ? prefixSum[nums.length - 1] : prefixSum[nums.length - 1] - prefixSum[stack.peek()]) * nums[minIndex]);
        }
        return (int) (max % 1000000007);
    }

上述代码 25 行讲解 当时学习有疑惑!

结算的是:minIndex位置的数字作为最小值情况下,nums[minIndex] * 扩展最大的子数组累加和

(stack.isEmpty() ? prefixSum[i - 1] : prefixSum[i - 1] - prefixSum[stack.peek()]) = 扩展最大的子数组累加和

分为两种情况 m代表minIndex

1. m 的左边没有比m小的数字。也就是在m位置弹出之后,stack是空的,比如

5 3 3 4 2 5 6 1 ...

0 1 2 3 4 5 6 7 ...

m i

比如m==4位置,因为i==7位置的数字,m 从单调栈弹出。

那么,m左边没有比nums[4]小的数字 那么此时就是

prefixSum[i-1] * nums[minIndex],也就是:prefixSum[6] * nums[4]

2. m的左边有比m小的数字。也就是在m位置弹出之后,stack不是空的,比如

5 1 3 4 2 5 6 1 ...

0 1 2 3 4 5 6 7 ...

m i

比如m==4位置,因为i==7位置的数字,m从单调栈弹出。

此时,m左边有比nums[4]小的数字,在1位置。此时就是 : (prefixSum[i-1] - prefixSum[stack.peek()]) * nums[m],

也就是:(prefixSum[6] - prefixSum[1]) * nums[4],也就是nums[2…6]的累加和 * nums[4]

public static int maxSumMinProduct(int[] arr) {
		int size = arr.length;
		long[] sums = new long[size];
		sums[0] = arr[0];
		for (int i = 1; i < size; i++) {
			sums[i] = sums[i - 1] + arr[i];
		}
		long max = Long.MIN_VALUE;
        // 数组模拟实现栈
		int[] stack = new int[size];
		int stackSize = 0;
		for (int i = 0; i < size; i++) {
			while (stackSize != 0 && arr[stack[stackSize - 1]] >= arr[i]) {
				int j = stack[--stackSize];
				max = Math.max(max,
						(stackSize == 0 ? sums[i - 1] : (sums[i - 1] - sums[stack[stackSize - 1]])) * arr[j]);
			}
			stack[stackSize++] = i;
		}
		while (stackSize != 0) {
			int j = stack[--stackSize];
			max = Math.max(max,
					(stackSize == 0 ? sums[size - 1] : (sums[size - 1] - sums[stack[stackSize - 1]])) * arr[j]);
		}
		return (int) (max % 1000000007);
	}
  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值