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)的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode——乘积最大子数组

LeetCode--152--乘积最大子序列(python)

Leetcode 152每日一题:乘积最大子数组

leetcode中等152乘积最大的子数组中等

贪心算法(leetcode152. 乘积最大子数组)

LeetCode-152-乘积最大子数组