算法--滑动窗口和单调栈

滑动窗口

滑动窗口是一种想象出来的数据结构:
滑动窗口有左边界L和有边界R
在数组或者字符串或者一个序列上,记为S,窗口就是S[L..R]这一部分
L往右滑意味着一个样本出了窗口,R往右滑意味着一个样本进了窗口
L和R都只能往右滑

在给定一个整数数组和滑动窗口大小的情况下,需要在每次窗口滑动时更新窗口内的最大值。双端队列可以用来存储窗口内的最大值元素的索引,从而在 O(1) 时间内更新最大值

保持双端队列从头到尾是以大到小,数字均从尾进出,当新进来的数比双端队列的数大时,就把队列里面小的数依次从尾部弹出。窗口往右划的过程中,左边的数过期就从头部弹出。这样就能从双端队列头部数字得到窗口内最大的数

题目1:假设一个固定大小为W的窗口,依次划过arr,返回每一次滑出状况的最大值。例如,arr=[4,3,5,4,3,3,6,7],W=3,返回:[5,5,5,4,6,7]

public static int[] getMaxWidow(int[] arr,int w){
	if(arr=null || w<1 || arr.length<w){
		return null;
	}
	//其中放的是位置,arr[位置]
	//双端队列
	LinkedList<Integer> qmax=new LinkedList<Integer>();
	int[] res=new int[arr.length-w+1];
	int index=0;
	for(int i=0;i<arr.length;i++){
		while(!qmax.isEmpty()&&arr[qmax.peekLast()]<=arr[i]){
			qmax.pollLast();
		}
		qmax.addLast(i);
		if(qmax.peekFirst()==i-w){
			qmax.pollFirst();
		}
		if(i>=w-1){
			res[index++]=arr[qmax.peekFirst()];
		}
	}
	return res;
	
}

题目二:
给定一个整型数组arr,和一个整数num,某个arr中的子数组sub,如果想达标,必须满足:sub中最大值-sub最小值<=num,返回arr中达标子数组的数量

public static int getNum(int[] arr,int num){
	if(arr==null || arr.length==0){
		return 0;
	}
	//存储当前窗口最小值的索引。
	LinkedList<Integer> qmin=new LinkedList<Integer>();
	//存储当前窗口最大值的索引。
	LinkedList<Integer> qmax=new LinkedList<Integer>();
	int L=0;  int R=0;
	int res=0;
	while(L<arr.length){
		while(R<arr.length){
			while(!qmin.isEmpty()&&arr[qmin.peekLast()]>=arr[R]){
				qmin.pollLast();
				
			}
			qmin.addLast(R);
			while(!qmax.isEmpty()&&arr[qmax.peekLast()]<=arr[R]){
				qmax.pollLast();
				
			}
			qmax.addLast(R);
			//如果当前窗口的最大值和最小值之差大于 num,则停止扩展窗口。
			if(arr[qmax.getFirst()]-arr[qmin.getFirst()]>num){
				break;
			}
			R++;
			
		}
		res+=R-L;
		if(qmin.peekFirst()==L){
			qmin.pollFirst();
		}
		if(qmax.peekFirst()==L){
			qmax.pollFirst();
		}
		L++;
	}
	return res;
	
}

单调栈

找某个数左右两边离他最近的比它小的数字在哪

准备一个栈,从栈底到栈顶遵循从小到大的,碰到一个不遵循的,就把数从栈顶弹出,该位置的数的右边离你最近最小的就是那个让你弹出去的数,你所压在栈底下的那个数则是你左边离你最近最小的数

//arr=[3,2,1,4,5]
//[0:[-1,1]
// 1:[-1,2]
//]

public static int[][] getNearLess(int[] arr) {
		int[][] res = new int[arr.length][2];
        //List<Integer> 放的是位置,同样值的东西,位置压在一起
		Stack<List<Integer>> stack = new Stack<>();
		for (int i = 0; i < arr.length; i++) { // i -> arr[i] 进栈
            //底->顶,小->大
			while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {
				List<Integer> popIs = stack.pop();
                //取位于下面位置的列表中,最晚加入的那个
				int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
				for (Integer popi : popIs) {
					res[popi][0] = leftLessIndex;
					res[popi][1] = i;
				}
			}
			if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {
				stack.peek().add(Integer.valueOf(i));
			} else {
				ArrayList<Integer> list = new ArrayList<>();
				list.add(i);
				stack.push(list);
			}
		}
		while (!stack.isEmpty()) {
			List<Integer> popIs = stack.pop();
			int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
			for (Integer popi : popIs) {
				res[popi][0] = leftLessIndex;
				res[popi][1] = -1;
			}
		}
		return res;
	}

题目:给定一个只包含正整数的数组arr,arr中任何一个子数组sub,一定都可以算出(sub累加和)*(sub中最小值)是什么,那么所有子数组中,这个值最大是多少

以i位置做为最小值的子数组,使其子数组范围尽量最大

public class Solution {
    public int maxSumMinProduct(int[] arr) {
        int n = arr.length;
        int maxSum = 0;
        Stack<Integer> stack = new Stack<>();

        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
                int index = stack.pop();
                int left = stack.isEmpty() ? -1 : stack.peek();
                int sum = (index - left - 1) * arr[index]; // 计算以 arr[index] 为最小值的子数组的和
                if (left != -1) {
                    sum += (left + n - index) * arr[index]; // 如果左边有元素,加上左边和右边的和
                }
                maxSum = Math.max(maxSum, sum); // 更新最大值
            }
            stack.push(i);
        }

		//处理栈中剩余的元素。
		//这些索引代表了以它们为最小值的子数组,其左侧没有更小的元素。
		//计算这些剩余子数组的和,并更新最大值 maxSum

        while (!stack.isEmpty()) {
            int index = stack.pop();
            int left = stack.isEmpty() ? -1 : stack.peek();
            int sum = (index - left - 1) * arr[index];
            if (left != -1) {
                sum += (left + n - index) * arr[index];
            }
            maxSum = Math.max(maxSum, sum);
        }

        return maxSum;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] arr = {1, 2, 3, 4, 5}; // 示例数组
        int result = solution.maxSumMinProduct(arr);
        System.out.println("最大值是: " + result);
    }
}

总结:

滑动窗口

滑动窗口通常用于解决与数组或字符串中的连续子序列相关的问题,特别是当问题可以归结为“找到满足特定条件的子序列”时。具体来说,当你需要:

  1. 找到子序列的最大或最小和/乘积:例如,找到数组中和最大的连续子数组。
  2. 满足特定条件的子序列:例如,找到所有字符都是奇数的最长子字符串。
  3. 维护一个窗口内的状态:例如,找到窗口内所有元素都是正数的最长子数组。
  4. 计数问题:例如,找到数组中不同整数对的最大数量,使得一对数的和为特定值。

单调栈

单调栈通常用于解决与下标相关的问题,特别是当问题涉及到维护一个递增或递减的序列时。具体来说,当你需要:

  1. 找到下一个/上一个大/小的元素:例如,找到数组中每个元素右侧/左侧的第一个比它大/小的元素。
  2. 维护一个单调递增或递减的序列:例如,使用单调栈可以有效地找到数组中每个元素的下一个最小值。
  3. 处理与栈有关的括号匹配问题:例如,验证括号是否正确闭合或找到匹配的括号。
  4. 优化空间复杂度:相比于滑动窗口,单调栈可以在 O(n) 的空间复杂度内解决问题,因为它只存储与当前元素相关的元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值