左神算法笔记(十六)——单调栈结构

单调栈解决的问题

单调栈解决的问题是在一个数组中想知道所有数中,左边离他近的比他大的和右边离他近的比他大的数
思考的问题:如果知道所有数上得到上述要求,同时复杂度满足O(N)。

单调栈结构:单调栈内,从栈底到栈顶满足从大到小。
例如:5(0)4(1)3(2)6(3)后面括号代表所属位置

入栈的过程:

  1. 5(0)压入栈然后4(1)比5(0)小,所以将4(1)压入栈中 第三步因为3(2)比4(1)小,所以也压入栈中
  2. 6(3)比3(2)要大,此时弹出栈顶的值3(2),并且将括号内的角标改成3变成3(3),代表对于3(2)这个数而言,右边第一个比他大的数位于3的位置。
  3. 3(2)左边离他最近的比他大的值就是在3的栈中的下一个值,也就是4(1)。
  4. 比较完之后将当前的6(3)放入数组之中,之前获得了信息的值可以直接进行表示了,不需要再放入栈中。
  5. 等到最后栈中还存在内容,则此时的所有数据单独弹出,此时右边没有比他大的数,栈中下面的数值是他的左边的比他大的数值。
  6. 如果出现相等数的情况,则此时下标压在一起,弹出是也一起弹出,多次计算。
    由于每个数都是进栈一次出栈一次,所以复杂度为O(N)。

应用

构造数组的MaxTree

左神算法笔记(十六)——单调栈结构_第1张图片

对于这个题,可以利用大根堆的思路解决题目,按照大根堆解决整个题目。
同时也可以采用单调栈的思路来解决这个问题。

思路:

  1. 每一个数都可以找到左边离的最近比他大的和右边离的最近比他大的(下面用左边和右边简称)
  2. 已知数组中没有重复值,如果这个节点没有左边和右边,则他就是全局最大值
  3. 如果一个数没有左边或者没有右边,则有唯一的父节点
  4. 如果左右两边都有,则选择较小的那个挂在底下作为子节点

上述流程解决了形成了每个节点都只有一个父节点和一个节点最多只能有两个子节点的问题(第4步决定了最多只能有两个子节点)

代码

求最大子矩阵的大小左神算法笔记(十六)——单调栈结构_第2张图片

思路

总体思路是将矩阵从第0行到最后一行,依次将每一行当做底,形成一个直方图,求解直方图的面积最大,也就是包含的1最多。

  1. 将第0行作为底,所有的长方形中,哪个长方形中含有的1最多。【1011】
  2. 此时再求以第1行作为底,所有的第0行和第1行所有的长方形中包含的1最多。【2122】
  3. 再将第2行的值打底,此时哪个长方形包含的1最多。【3230】
  4. 。。。
    (依次类推,可以求解出矩阵中所有的矩阵包含1的数目,此时求解其中最大值就是最大的值)

复杂度:由于每次遍历一行,最后求解的也就是将整个矩阵遍历,所以最后的结果也就是O(n*m)。

代码

//数组表示直方图最大的面积
public static int maxRecFromBottom(int[] height){
	if(height == null || height.length ==0){
		return 0;
	}
	int maxArea = 0;
	Stack<Integer> stack = new Stack<Integer>();
	//这个for循环遍历数组的每一个数
	for(int i = 0;i<height.length;i++){
		//当栈不为空,且当前数小于等于栈顶
		while(!stack.isEmpty() && height[i] <= height[stack.peek()]){
			//第一次循环j=0,k表示弹出之后底下的下标,如果没有东西则为-1.
			int j = stack.pop();
			int k = stack.isEmpty() ? -1:stack.peek();
			//左边界为k,有边界为i,乘上j位置上的高,求出当前的值
			int curArea = (i-k-1)*height[j];
			maxArea = Math.max(maxArea,curArea);
		}
		stack.push(i);
	}
	//最后栈内可能还剩东西,这个while在结算栈中剩余的内容
	while(!stack.isEmpty()){
		int j = stack.pop();
		int k = stack.isEmpty() ? -1:stack.peek();
		int curArea = (height.length-k-1)*height[j];
		maxArea = Math.max(maxArea,curArea);
	}
	return maxArea;
}


//原问题的解决
public static int maxRectSize(int[][] map){
	if(map == null || map.length == 0 || map[0].length == 0){
		return 0;
	}
	int maxArea = 0;
	int[] height = new int[map[0].length];
	for(int i=0;i<map.length;i++){
		for(int j = 0;j<map[0].length;j++){
			//如果j的位置为0,则将整个高度变为0,否则就在原来的长度基础上加1
			height[j] = map[i][j] == 0? 0:height[j]+1;
		}
		maxArea = Math.max(maxRecFromBottom(height),maxArea);
	}
	return maxArea;
}

题目

给定一个数组,代表一个环形的山,每一座山上会放烽火,如果两座山相邻,相邻两座山必定可以互相看见,如果两座山之间的两条环路中只要有一条路中中间的山高度都不比两个山高,则两座山之间也可以相互看见。给定一个数组,给出能相互看见的山峰有多少对。

如果数组中只有一个数,则有0对,如果有2个,则有1对。如果数组中有i个数,且值都不一样,则有(2*i-3)对。

思路

整体思路是由小的山峰去找大的山峰,假如我们找到了整个山峰中的最高和次高,两个山峰中间有个值arr[i],作为小的值,去找大的,以他去找两边较大的。题目可能存在相等的情况。
单调栈解决问题:

  1. 先找到全局最大值,如果存在多个最大值,只需要找一个就行,作为最开始遍历的位置。
  2. 准备一个单调栈,此时将最大值放进,同时放入标记1,表示此时共有1个最大值。
  3. 按照单调栈进行,如果存在新来的值比栈顶值大,栈顶内容弹出时,表示找到顺时针的一边最大值,同时如果栈内还存在值,则另一边也有逆时针最大值,此时一共找到两对。
  4. 遇见重复数组,则将相同值压在一起,并且标记值+1。
  5. 当存在多个重复值时,任意两个数值均可相互看见,并且左边和右边都有最大值,所以总的数值为C2,k(k表示重复的次数)+k*2。
  6. 最后还在栈中没有结算出来的,如果最大值数目为1,则每个次大值只能找到一边,无法找到两边,此时为c2,k+k,如果最大值数目大于1,则还是第五步中的值

代码

public static long communications(int[] arr){
	if(arr == null || arr.length<2){
		return 0;
	}
	int size = arr.length;
	int maxIndex = 0;
	//整个for循环就是在整个数组中找到最大值的位置
	for(int i =0;i<size;i++){
		maxIndex = arr[maxIndex]<arr[i] ? i:maxIndex;
	}
	//value就是最大值
	int value = arr[maxIndex];
	int index = nextIndex(size,maxIndex);
	long res = 0L;
	//新建了一个栈,并且将最大值放进去,此时就这一条记录
	Stack<Pair> stack = new Stack<Pair>();
	stack.push(new Pair(value));
	//因为遍历的起始位置是maxIndex如果最后回到了maxIndex,则认为遍历结束
	while(index != maxIndex){
		//拿到数组中的当前值
		value = arr[index];
		//
		while(!stack.isEmpty() && stack.peek().value<value){
			int times = stack.pop().times;
			//内部山峰对的和,下面两行直接合道一起也可以
			res +=getInternalSum(times)+times;
			res +=stack.isEmpty() ? 0 : times;
		}
		//如果当前数等于栈顶的值,则将栈顶的times加一
		if(!stack.isEmpty() && stack.peek().value == value){
			stack.peek().times++;
		}else{
			stack.push(new Pair(value));
		}
		index = nextIndex(size,index);
	}
	//最后单独定制倒数第二条记录
	while(!stack.isEmpty()){
		int times = stack.pop().times;
		res += getInternalSum(times);
		if(!stack.isEmpty()){
			res+= times;
			if(stack.size()>1){
				res +=times;
			}else{
				res += stack.peek().times >1? times :0;
			}
		}
	}
	return res;
}

//在环形的函数中,到底就到0的位置,不到就加一
public static int nextIndex(int size,int i){
	return i<(size-1)?(i+1):0;
}
//存储的是值以及出现的次数
public static class Pair{
	public int value;
	public int times;
	public pair(int value){
		this.value = value;
		this.times = 1;
	}
}
//组合的实现C2,n
public static long getInternalSum(int n){
	return n == 1L ? 0L:(long)n*(long)(n-1)/2L;
}

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