1、设计一个能获取当前栈的最小值的栈
设计一个能获取当前栈的最小值的栈。
大致思路:
1、申请两个栈,一个普通栈stackData,一个能获取最小值的栈stackMin;
2、给两个栈中同时压入数据,压数规则:同时给两个栈压入数据,当要压入的数比stackMin栈顶的元素小时,将该数同时压入两栈,如果要压入的数大于stackMin栈顶的元素小时,将该数压入stackData中,将stackMin栈顶的数重新再压入stackMin,具体细节详见代码注释。
举例:依次压入 3、4、5、1、2、1 的过程中,stackData 和 stackMin 的变化如图所示:
具体代码
public class TwoStack {
/**
* TwoStack为设计的可以获取当前栈最小值的栈
* 可以看做在TwoStack的里边有两个子栈,共同完成获取最小值的功能
*/
//申请两个栈,一个普通栈stackData,
//一个能获取最小值的栈stackMin(从栈底到栈顶依次减小的栈)
public Stack<Integer> stackData;
public Stack<Integer> stackMin;
//初始化两个栈
public TwoStack() {
stackData = new Stack<Integer>();
stackMin = new Stack<Integer>();
}
//push方法,为了和栈的方法push区别开命名ppush
public void ppush(int newNum){
//stackData的push,直接将数压栈
stackData.push(newNum);
//判断最小栈是否有数,没有的话直接将数压栈
if(stackMin.isEmpty()){
stackMin.push(newNum);
//当普通栈的数小于最小栈的数,直接将数压栈
//比较的是每次要入栈的数和最小栈的栈顶元素
}else if(newNum < this.getmin()){
stackMin.push(newNum);
}else{
//若普通栈的数大于等于最小栈的数,将最小栈的栈顶元素再压一次进栈,
//Stack的peek方法是返回栈顶的元素但不移除它
int newMin = stackMin.peek();
stackMin.push(newMin);
}
}
//pop方法,为了和栈的方法pop区别开命名ppop
public int ppop(){
if(stackData.isEmpty()){
throw new RuntimeException("普通栈空了");
}
stackMin.pop();
return stackData.pop();
}
//获取最小值的方法,本质就是返回最小值栈的栈顶
private int getmin() {
if(stackMin.isEmpty()){
throw new RuntimeException("最小数栈空了");
}
return stackMin.peek();
}
public static void main(String[] args) {
TwoStack twoStack = new TwoStack();
twoStack.ppush(5);
twoStack.ppush(3);
twoStack.ppush(4);
twoStack.ppush(22);
twoStack.ppush(9);
System.out.println(twoStack.getmin());
}
}
测试结果
2、单调栈
概念
从栈顶到栈底依次增加或减小的栈。
做个题目了解单调栈的特点和性质
2.1 找出数组中任意元素左右两边最近且比它小的元素位置
给定一个不含有重复值的数组 arr,找到每一个 i 位置左边和右边离 i 位置最近且值比 arr[i]小的位置。返回所有位置相应的信息。
比如:
输入: arr = [3,4,1,5,6,2,7]
返回:{[-1, 2], [0, 2], [-1, -1], [2, 5], [3, 5], [2, -1], [5, -1]}
解题思路:
这个题目利用单调栈结构可以使时间复杂度降低到O(N)。首先题目是没有重复值的数组,注意,如果是有重复值的数组稍有区别。
1、先准备一个栈,用来存放所给数组元素的位置;
2、如果找到每一个 i 位置左边和右边离 i 位置最近且值比 arr[i]小的位置,那么需要让 stack 从栈顶到栈底的位置所代表的值是严格递减的;如果找到每一个 i 位置左边和右边离 i 位置最近且值比 arr[i]大的位置,那么需要让 stack 从栈顶到栈底的位置所代表的值是严格递增的,本题目采用前者;
3、循环遍历数组压入栈中,压栈和出栈方式:
- 当前栈为空,压入arr[0] = 3;
- 压入arr[1] = 4时,因为4 > 3,满足从栈顶到栈底单调减原则,所以压入;
- 压入arr[2] = 1时,因为1 < 4 , 违反了从栈顶到栈底单调减原则,所以弹出arr[1] = 4,此时,弹出位置的下面位置就是距离它左边最近的比它小的值,即为3,距离它右边最近比它小的值就是当前要压入的值,即为1,;
- 压入arr[3] = 5,因为5 > 1,满足从栈顶到栈底单调减原则,所以压入;
- 压入arr[4] = 6,因为6 > 5,满足从栈顶到栈底单调减原则,所以压入;
- 压入arr[5] =2,因为2 < 6,违反了从栈顶到栈底单调减原则,所以依次弹出arr[4] = 6,arr[3] = 5,得到arr[4] = 6左右距离最近且小于它的值的位置[3,5]和arr[3] = 5左右距离最近且小于它的值的位置[2,5];
- 压入arr[6] = 7,因为7 > 2,满足从栈顶到栈底单调减原则,所以压入;
至此,所有元素已经全部压入,开始弹出栈中剩余元素,弹出arr[6] = 7,得到它的左右最近且比他小的位置[5,-1] (栈顶元素没有右侧最近的数,此位置记为-1,同理栈底左侧位置记为-1),弹出arr[5] =2,得到它的左右最近且比他小的位置[2,-1] ,弹出arr[2] =1,得到它的左右最近且比他小的位置[-1,-1] 。
具体代码
public class getNearLessNoRepeat {
public static int[][] getNearLessNoRepeat(int[]arr){
int[][]res = new int[arr.length][2];
Stack<Integer> s = new Stack<>();
//循环遍历数组元素
for (int i = 0; i < arr.length; i++) {
//这段代码就是违反了单调栈原则之后产生的弹出元素操作
while(!s.isEmpty() && arr[i] < arr[s.peek()]){
int popIndex = s.pop();
//如果栈中只有一个元素,且被弹出了,则它的左侧要求的值为-1,
//否则就是弹出之后栈中还有元素,则它的左侧要求的值为弹出值的下面一个元素
int leftLessIndex = s.isEmpty() ? -1 : s.peek();
res[popIndex][0] = leftLessIndex;
//右侧值为当前要压入的元素位置
res[popIndex][1] = i;
}
s.push(i);
}
//当所有元素全部压入,部分弹出时,栈中还剩余一部分元素
while(!s.isEmpty()){
int popIndex = s.pop();
int leftLessIndex = s.isEmpty() ? -1 : s.peek();
res[popIndex][0] = leftLessIndex;
//此时栈中所有元素右侧都没有要求的值了,所以都为-1
res[popIndex][1] = -1;
}
return res;
}
public static void main(String[] args) {
int []arr = new int[]{3,4,1,5,6,2,7};
System.out.println(Arrays.deepToString(getNearLessNoRepeat(arr)));
}
}
测试结果
2.2 求最大子矩阵的大小
给定一个整型矩阵 map,其中的值只有 0 和 1 两种,求其中全是 1 的所有矩形区域中,最大的矩形区域为 1 的数量。
比如:
map为矩阵,红框内即为所求,一共有6个1,返回值为6。
解题思路:
将问题转化为单调栈结构处理。
1、遍历矩阵每一行元素,统计以当前行作为底的情况下,每个位置往上的 1的数量。使用高度数组 height 来表示。
- 遍历到第一行时,height={1,0,1,1};
- 遍历到第二行时,height={2,1,2,2};
- 遍历到第三行时,height={3,2,3,0};
height 数组的更新可以这样表示: height[j] = map[i][j]==0 ? 0 : height[j]+1。最终得到这样一个柱状图:
此时问题转化为求虚线区域的面积。
求虚线区域的面积
每一个柱子往左右两边扩,当遇到离它最近且小于它高度或到达左右两边界的时候停止扩,得到每个柱子可以扩出的面积,所有面积里面最大的即为所求。此时,问题就转化成了利用单调栈结构找出数组中任意元素左右两侧距离它最近且比它小的数。这个问题可以参考单调栈–找出数组中任意元素左右两边最近且比它小的元素位置,了解详细过程。
具体代码
public class maxAreaDemo {
public static int maxRecSize(int[][]map){
//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++) {
height[j] = map[i][j] == 0 ? 0 : height[j] + 1;
}
maxArea = Math.max(maxRecFromBottom(height),maxArea);
}
return maxArea;
}
//根据柱状的高度数组,利用单调栈结构求最大面积
private static int maxRecFromBottom(int[] height) {
if(height == null || height.length == 0){
return 0;
}
int maxArea = 0;
Stack<Integer> s = new Stack<>();
//找到每个元素离它最近的且比它小的元素
for (int i = 0; i < height.length; i++) {
while (!s.isEmpty() && height[i] <= height[s.peek()]){
//j为栈顶的值,同时也代表每个柱状体的高度所在的位置
int j = s.pop();
//k为所求元素左边要求的值的位置
int k = s.isEmpty() ? - 1 : s.peek();
// i为所求元素右边要求的值的位置
int curArea = (i - k - 1)*height[j];
maxArea = Math.max(maxArea,curArea);
}
s.push(i);
}
//此时所有元素全部压入栈中
while(!s.isEmpty()){
int j = s.pop();
int k = s.isEmpty() ? -1 : s.peek();
int curArea = (height.length - k -1)*height[j];
maxArea = Math.max(maxArea,curArea);
}
return maxArea;
}
public static void main(String[] args) {
int [][] map = {{1,0,1,1},{1,1,1,1},{1,1,1,0}};
System.out.println(maxRecSize(map));
}
}
测试结果