数据结构与算法-单调栈2

文章介绍了一种针对特定问题的单调栈优化,用于解决寻找给定正数数组中所有子数组(sub累加和)*(sub中的最小值)最大值的问题。通过计算前缀和与改进的单调栈,确定每个元素左边和右边第一个比它小的元素,从而有效地找出最大值。这种方法在处理有重复值的数组时,对于最后一个重复值的计算是正确的,但中间重复值的某些情况可能不准确。代码实现详尽,包括主函数和辅助的单调栈函数,以及测试用例验证了算法的正确性。
摘要由CSDN通过智能技术生成

这里是解了一个新的题目,然后对于有重复值的单调栈做了一些改进(只适用于特殊题目):有重复值的单调栈不再使用ArrayList或者LinkedList,而是像无重复值的那样直接使用下标,这种方法能保证最终的正确性,具体就是对于最后一个元素的左边第一个比他小的元素和右边第一个比他小的元素的计算是正确的,但是对于中间的重复值,右边第一个比他小的元素的计算可能是不正确的。

题目如下:

给定一个只包含正数的数组arrarr中任何一个子数组sub

一定都可以算出(sub累加和 )* (sub中的最小值)是什么,

那么所有子数组中,这个值最大是多少?

解题思路:尝试所有位置,找以当前数为最小值的范围的(sub累加和 )* 当前数的值,所有位置都会尝试,结果必在其中。

具体的看代码:

package dataStructure.danDiaoZhan.practice;


import java.util.Stack;

/**
 * 给定一个只包含正数的数组arr,arr中任何一个子数组sub,
 * 一定都可以算出(sub累加和 )* (sub中的最小值)是什么,
 * 那么所有子数组中,这个值最大是多少?
 */
public class AllTimesMinToMax {
    public static int process(int[] nums) {
         if(nums == null || nums.length == 0) {
             return -1;
         }
         //先设置为0,因为是正数数组,所以后面求的值都会比这个大
         int allTimesMin = 0;
         //算一个前缀和数组
         int[] preSum = new int[nums.length];
         for(int i = 0; i < nums.length; i++) {
             preSum[i] = i == 0? nums[i] : nums[i] + preSum[i - 1];
         }
         //对于每个位置,计算它的左边第一个比它小的元素和右边第一个比它小的元素
         int[][] nearLessArr = getNearLess(nums);
         for(int i = 0; i < nums.length; i++) {
             //nearLess是表示左边第一个比他小和右边第一个比它小的数组
             int[] nearLess = nearLessArr[i];
             //int leftBound = nearLess[0] == -1? 0 : nearLess[0];
             //找当前以nums[i]为最小元素的右边界,如果右边第一个比它小的数是-1
             //右边界就持续到数组最后一个元素,如果不是就持续到nearLess[1] - 1(nearLess[1]是第一个比他小的,那nearLess[1] - 1就是最后一个大于等于它的)
             int rightBound = nearLess[1] == -1? nums.length - 1 : nearLess[1] - 1;
             //System.out.println("nearLess[0]="+nearLess[0]);
             //这里一定要注意括号
             //(nearLess[0] == -1? 0 : preSum[nearLess[0]])是找当前范围前一个的累加和
             //如果nearLess[0] == -1表示左边没有比它小的,也就是以它为最小元素可以扩到数组开头
             //否则扩到nearLess[0]+1
             int preSumCurRange = preSum[rightBound] - (nearLess[0] == -1? 0 : preSum[nearLess[0]]);
             //每次比较计算最大值
             allTimesMin = Math.max(allTimesMin, nums[i] * preSumCurRange);
         }
         return allTimesMin;
    }

    /**
     * 改进后的单调栈,这里是可以兼容有重复值的情况,Stack里的数据不再是ArrayList或者LinkedList
     * 而是使用Integer类型,表示原来在数组中的下标
     * 这种的话对于重复值的情况,如果不是重复值的最后一个,那弹出时算的结果应该是不对的
     * (被自己的重复值元素弹出时右边第一个比他小的不对,因为是被重复值弹出的,实际上还没有找到右边第一个比他小的数),
     * 但是对于我们这个题来说是不影响结果的
     * 因为重复值的最后一个弹出的时候(包括最后独立清算的时候弹出)算的结果肯定是对的
     * @param nums
     * @return
     */
    public static int[][] getNearLess(int[] nums) {
        int[][] nearLessArr = new int[nums.length][2];
        //单调栈,依然是自底到顶从小到大,里面放的是数组中的下标
        Stack<Integer> stack = new Stack<>();
        for(int i = 0; i < nums.length; i++) {
            //如果栈不是空的且栈顶元素大于等于当前的元素,把栈顶依次弹出
            while(!stack.isEmpty() && nums[stack.peek()] >= nums[i]) {
                int popNumIndex = stack.pop();
                //左边第一个比他小的元素的计算是正确的
                nearLessArr[popNumIndex][0] = stack.isEmpty()? -1 : stack.peek();
                //对于右边第一个比他小的数,如果是被重复值弹出的这里计算的是重复值,但是这种是不对的
                // 因为重复值并不是右边第一个比他小的(他们相等),但是最后一个重复值肯定是正确的
                nearLessArr[popNumIndex][1] = i;
            }
            //当前元素下标压入栈
            stack.push(i);
        }
        //最后的独立结算过程
        while(!stack.isEmpty()) {
            int popNumIndex = stack.pop();
            nearLessArr[popNumIndex][0] = stack.isEmpty()? -1 : stack.peek();
            nearLessArr[popNumIndex][1] = -1;
        }
        return nearLessArr;
    }

    public static void main(String[] args) {
        //int[] nums = {5,5,5};
        int[] nums = {1,2,2,2,2,2,1};
        int allTimeMinMax = process(nums);
        System.out.println(allTimeMinMax);
    }
}

代码我觉得已经注释到详细的不能再详细了,如果还是不懂,看一下我下面的过程分析图:

一般场景的分析

结果是对的。 

极端场景

可以看到还是正确的 

还是不懂的话可以私信我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值