左神算法笔记(十五)——窗口

窗口概念本身并不难理解,窗口从左向右滑动,窗口本身相当于一个固定长度的窗子,依次向右边滑动,从而在中间会有左边减值,右边增加值。

窗口内最大值的求解

双端队列,代价都是O(1),不需要这个结构表示第几个数是最大值,只需要双端队列可以依次增加或减少数值。
双端队列中每次保存两个值,当前的数值和当前的位置信息。
加数的逻辑:

  1. 每次小的值从右边进入双端队列,整个队列形成从大到小排列的顺序。

  2. 如果出现新的数值大于最右边的小数值,则将小的数值弹出,直到放得下新的数值。

  3. 如果出现相等的数值,则将先前的数值弹出,将新的数值保存
    减数的逻辑:
    L移动的时候,分析当前最大值所在的位置信息是否过期,如果过期则弹出,没过期则显示当前的最大值。

    加数的逻辑中的原理解释:每次增加新的值的时候,如果新增的值比当前双端队列中保存的值小,则需要保留,因为窗口滑动过去之前的较大值之后后面现在的较小的值还有可能变成最大值。当新来了一个较大的值,比队列中的一些值大或相等,则可以直接将那些值删掉,因为接下来那些值存在的时候,新来的值一定存在,并且比那些值大,所以可以直接删掉。
    对于双端队列,可以只保存下角标,然后从原始数组中去读取数据值。

代码

public static int[] getMaxWindow(int[] arr,int w){
	if(arr == null || w<1 || arr.length <w){
		return null;
	}
	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);
		//判断首位上的数据是否过期,进行下标和i-w的判断,过期就弹出下标,不过期就继续下面的操作
		//窗口如果没有形成完全,则不会弹出。此处针对不同题目需要进行修改,看窗口是否要全完全显示之后才开始计算最大值
		if(qmax.peekFirst() == i-w){
			qmax.pollFirst();
		}
		//如果i还在窗口之中,则返回第一个数值。
		if(i>=w-1){
			res[index++] = arr[qmax.peekFirst()];
		}
	}
	return res;
}

最大值减去最小值小于或等于num的子数组数量

给定数组arr和整数num,返回一段数组中最大值减去最小值小于num的数目,要求:数组长度为N,请实现时间复杂度为O(N)的解法。

所有子数组的数目等于等差数列n(n-1)/2。先给出一种暴力解法,暴力解法中,其实就是首先两个for循环遍历所有的子数组,然后在所有的过程中进行一个最大减最小。复杂度O(N^3),很高,可能不得分。

暴力解法:

public static int getNum1(int[] arr,int num){
	int res = 0;
	for(int start = 0;start <arr.length;start++){
		for(int end = start;end<arr.length;end++){
			if(isValid(arr,start,end,num){
				res++;
			}
		}
	}
	return res;
}
public start boolean isvalid(int[] arr,int start,int end,int num){
	int max = Integer.MAX_VALUE;
	int min = Integer.MIN_VALUE;
	for(int i = start;i<=end;i++){
		max = Math.max(max,arr[i]);
		min = Math.min(min,arr[i]);
	}
	return max-min <= num;
}

新解法:如果中间某个数组从L——R达标,则任何其中的子数组也全部达标。同理,如果一个子数组不达标,则将其任意向外扩,则那些数组也不达标,利用这样的性质整双端队列。

  1. L停留在0位置,如果一直达标,则将R向右扩,直到扩到某个位置X,再往后扩一个,则不达标。
  2. 窗口内最大值的更新结构和窗口内的最小值更新结构可以很简单获得最大和最小值,从而进行计算。
  3. 以0开头的子数组达标数量则计算了出来,一共有x+1长度的数组达标,他的所有子数组全部达标。
  4. 将L来到1位置,此时将窗口最大值和最小值更新,此时R可能仍然可以向外扩,此时右可以得到以1位置开头的所有可行的数组
    因为没有回退,一直是当前位置向后走,总的复杂度为O(N)。

新解法

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 i = 0;//i为start,j为end
	int j = 0;
	int res = 0;
	//整个while循环是将R向右不断扩展
	while(i<arr.length){
		while(j<arr.length){
			while(!qmin.isEmpty() && arr[qmin.peeklast()] >= arr[j]){
				qmin.polllast();
			}
			qmin.addLast(j);
			while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[j]){
				qmax.pollLast();
			}
			qmax.addLast();
			if(arr[qmax.getFirst()]- arr[qmin.getFirst()] > num){
				break;
			}
			j++;
		}
		//判断是否进行下一个数开头
		if(qmin.peekFirst() == i){
			qmin.pollFirst;
		}
		if(qmax.peekFirst() == i){
			qmax.pollFirst;
		}
		//一次性获取完以i开头的所有满足的数组
		res += j-i;
		i++;
	}
	return res;

你可能感兴趣的:(左神算法专栏)