栈与队列
实现最小栈
题目描述
- 实现一个特殊的栈,在实现栈的基本功能的基础上,再返回栈的最小元素的操作
- 要求:pop(),push(),getMin()时间复杂度都是O(1)
解题思路
- 设计上使用两个栈,一个栈保存当前栈中的元素记为stackData;另外一个栈用于保存每一步的最小值,记为minStack;
- 压入数据规则
设当前数据为num,先将其压入StackData,然后判断minStack是否为空,若为空,压入minstack,否则比较num与minStack的栈顶元素,当小于栈顶元素时,压入minStack - 弹出数据规则
先在stackData中弹出栈顶元素,记为value,然后比较minStack中的栈顶元素,如果minStack栈顶元素等于value,minStack的栈顶元素也弹出,否则不进行处理 - 查询最小值
minStack始终保存stackData中的最小值
- 压入数据规则
public class Stack1{
private Stack<Integer>dataStack;
private Stack<Integer> minStack;
public void push(int num){
if(this.minStack.isEmpty()){
this.minStack.push(num);
}else if(num<=this.getMin()){
this.minStack.push(num);
}
this.dataStack.push(num);
}
public int pop(){
int value=this.dataStack.pop();
if(value==this.minStack.peek()){
this.minStack.pop();
}
return value;
}
public int getMin(){
return this.minStack.peek();
}
}
由两个栈组成的队列
题目描述
编写一个类,用两个栈实现队列,支持队列的基本操作(add,poll,peek)
解题思路
由于栈的特点是先入后出,队列的特点是先进先出,使用两个栈正好能把顺序反过来;设计上使用两个栈,一个栈负责压入数据,记为pushStack,另外一个栈负责弹出数据popStack;
1. 压入数据的规则
pushStack正常压入数据,但是如果popStack不为空,不可将pushStack的数据压入到popStack;同时pushStack的数据要往popStack压入数据时,必须要一次性的将pushStack数据全部压入,add方法
2. 弹出数据规则
popStack()正常弹出数据即可。即poll,与peek方法,
3. 向popStack压入数据在poll,peek方法处执行
public class TwoStackQueue{
private Stack<Integer> pushStack;
private Stack<Integer> popStack;
public void add(int num){
pushStack.push(num);
}
public int poll(){
if(pushStack.empty()&&stackPush.empty()){
throw new RuntimeException("Queue is Empty");
}
else if(popStack.empty()){
while(!pushStack.empty()){
popStack.push(pushStack.pop());
}
}
return popStack.pop();
}
public int peek(){
if(pushStack.empty()&&stackPush.empty()){
throw new RuntimeException("Queue is Empty");
}
else if(popStack.empty()){
while(!pushStack.empty()){
popStack.push(pushStack.pop());
}
}
return popStack.peek();
}
}
两个队列实现栈
题目描述
编写一个类,用两个队列实现栈的功能,实现栈的push,pop方法
解题思路
定义两个队列queue1,queue2
1. push方法实现
判断queue1,queue2是否为空,将需要进栈的元素num,添加到不为空的一个队列的队尾,
2. pop方法实现
判断queue1,queue2是否为空,为说明方便,假设queue1队列不为空,要获得栈顶元素(队尾的元素,记为value),将队列中的元素,从队首到队尾的前一个元素依次弹出,添加到queue2(初始状态为空队列)队尾位置,最后弹出queue1队尾元素value,此时queue2队列为空,
public class TwoQueueStack{
private LinkedList<Integer> queue1;
private LinkedList<Integer> queue2;
public void push(int num){
if(queue1.isEmpty()){
queue2.add(num);
}
if(queue2.isEmpty()){
queue2.add(num);
}
}
public int pop(){
if(queue2.isEmpty()&&queue2.isEmpty()){
throw new RuntimeException("Stack is Empty");
}
if(queue1.isEmpty()){
while(queue2.size()>1){
queue1.add(queue2.poll());
}
int value= queue2.poll();
}
else if{//queue2.isEmpty()
while(queue1.size()>1){
queue1.add(queue1.poll());
}
int value= queue1.poll();
}
return value;
}
}
使用递归函数和栈操作逆序一个栈
题目描述
只能用递归函数来实现栈的逆序的操作
思路解析
两个递归函数:
- 递归函数1:将栈的栈底元素删除并且返回
- 递归函数2:逆序一个栈,将栈底元素删除之后的栈进行逆序,然后将栈底元素重新压入栈中
public class ReverseStack{
private Stack<Integer> stack;
public int getAndRemoveLastElement(){
int result=stack.pop();
if(stack.isEmpty()){
return result;
}else{
int res=getAndRemoveLastElement();
stack.push(result);
return res;
}
}
public void reverseStack(){
if(stack.isEmpty()){
return;
}
int res=getAndRemoveLastElement();
reverseStack();
stack.push(res);
}
}
用一个栈实现另一个栈的排序
题目描述
一个栈中的元素类型为整型,将该栈从栈顶到栈底从大到小进行排序,只允许申请一个栈,除此之外,可以申请新的变量,不能申请额外的数据结构
思路解析
将要排序的栈记为stack,辅助栈为help,比较操作在help栈中实现,具体操作如下:
1. 在stack进行pop操作,弹出的元素记为cur
2. 比较cur,help的栈顶元素
- 如果cur小于help的栈顶元素,直接将cur压入help栈中
- 如果cur大于help的栈顶元素,则将help栈中的元素逐一弹出,逐一压入stack中,直到cur小于或者等于help的栈顶元素;
3. 最后将help的元素压入stack中
public void sortStackByStack(Stack<Integer> stack){
Stack<Integer> help=new Stack<>();
while(!stack.isEmpty()){
int cur=stack.pop();
while(!help.isEmpty()&&cur>help.peek()){
stack.push(help.pop());
}
help.push(cur);
}
while(!help.isEmpty()){
stack.push(help.pop());
}
}
生成窗口最大值数组
题目描述
有一个整型数组arr,和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。
例如arr=[4,3,5,4,3,3,6,7],窗口大小为3
[4 3 5] 4 3 3 6 7 窗口的最大值为5
4 [3 5 4] 3 3 6 7 窗口的最大值为5
4 3 [5 4 3] 3 6 7 窗口的最大值为5
4 3 5 [4 3 3] 6 7 窗口的最大值为4
4 3 5 4 [3 3 6] 7 窗口的最大值为6
4 3 5 4 3 [3 6 7] 窗口的最大值为7
数组长度为n,窗口为w,将产生n-w+1
输出为:[5 5 5 4 6 7]
思路解析
使用双端队列实现窗口最大值的更新,双端队列qmax保存的是数组的索引。队首即为窗口的最大值的索引。
1. 假设遍历到arr[i],qmax的放入规,则:
- qmax为空,直接放入i,
- qmax不为空,取出当前qmax的队尾存放的下标j(其实是队列的最小值)
- 如果arr[i]>=arr[j],直接将j从qmax队列弹出,继续qmax的放入规则,说明arr[j]不可能是之后向右滑动窗口的最大值
- 如果arr[j]>arr[i],将小标i,加入到qmax队尾中,由于arr[i]可能是之后向右滑动窗口的最大值,
2. 要清除队列中最大值的失效值。当遍历的数组元素下标i,qmax队首元素为j,窗口的长度w,之间满足j=i-w;也就是元素arr[j]最大值已经失效了
3. 只要遍历数组元素下标i满足i>w-1;将qmax队首元素为某个窗口的最大值,保存即可。
public int[] getMaxWindow(int[]arr,int w){
if(arr==null||arr.length<w||w<0)
return null;
int index=0;
int res=new int[arr.length-w+1];
LinkedList<Integer> qmax=new LinkedList<>();
for(int i=0;i<arr.length;i++){
while(!qmax.isEmpty()&&arr[i]>=arr[qmax.peekLast()])
{
qmax.pollLast();
}
qmax.addLast(i);
if(i-w==qmax.peekFirst())//清除失效最大值
qmax.pollFirst();
if(i>w-1)
res[index++]=arr[qmax.peekFirst()];
}
return res;
}
求最大子矩阵的大小
题目描述
给定一个整型矩阵map,其中的值只有0,1;求其中全是1的所有矩形区域中,最大的矩形区域为1的数量。
例如 1 1 1 0 ,最大的矩形区域有3个1,返回1
map=[1 0 1 1
1 1 0 1
1 1 1 1]
其中最大的矩形区域有4个1,返回4
思路解析
如果矩阵的大小为O(NM),本题的时间复杂度为O(NM);
1. 矩阵的行数为N,以每一行做切割,统计以当前作为底的情况,每个位置上为1的数量,使用高度数组height表示,
- 以题目描述的map为例,从第一行做切割,height=[1 0 1 1]
- 以第二行做切割,更新height;height=[2 1 0 2];height的更新操作为height[i]=map[i][j]==0?0:height[j]+1;
- 以第三行做切割,更新height;height=[3 2 1 3]
2. 对于每次切割,利用更新的height数组求出以每一行为底的情况下,最大的矩形是什么
- 求解当前height数组组成的最大矩形,比如{3,2 ,3, 1},看出一个数组组成的直方图
1. 第1根高度为3的柱子无法向左扩展,右边为2, 也无法向右左扩展,则以第1根柱子为高度的矩形面积为3*1=3;
2. 第2根高度为2的柱子向左可以扩展一个距离,向右扩展1个距离,则以第2根柱子为高度的矩形面积为2*3=6;
3. 同理,以第3根柱子为高度的矩形面积为3*1=3
4. 同理,以第4根柱子为高度的矩形面积为1*4=4
- 使用栈来实现上述操作
1. 生成一个栈,记为stack,每遍历一个位置都会把位置压入到stack中
2. 当遍历到arr[i],栈为空时,直接压入,若栈不为空,比较栈顶位置所代表的值与arr[i]大小,若arr[i]较大,直接压入stack中;否则则把栈中存的位置不断弹出,直到某一个栈顶所代表的值小于arr[i],再把位置i压入
3. 在遍历到数组arr[i],弹出的栈顶位置为j,弹出栈顶之后,新的栈顶元素为k,那么可以知道以arr[j]为高度的柱子向右最多可以扩展到(i-1),因为j之所以被弹出,是由于遇到了**第一个**比位置j值小的位置;向左最多扩展到k+1;这是因为栈中k,j位置是相邻的,并且从栈顶到栈底的位置所代表的的值依次递减并且无重复的,由于k位置在栈中,arr[k+1...j-1]都是既大于arr[k],否则k会被弹出;同时由于j在栈中,并且与k相邻,所以arr[k+1...j-1]没有小于arr[j],否则j不可能与k相邻。
4. j位置的柱子能扩出来的最大矩形为(i-k-1)arr[j]
public int maxSize(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++){
height[j]=map[i][j]==0?0:height[j]+1;
}
maxArea=Math.max(maxArea,maxSizeFromBottom(height));
}
return maxArea;
}
public int maxSizeFromBottom(int [] height){
if(height==null||height.length==0)
return 0;
int maxArea=0;
Stack<Integer> stack=new Stack<>();
for(int i=0;i<height.length;i++){
while(!stack.isEmpty()&&height[i]<=height[stack.peek()]){
int j=stack.pop();
int k=stack.isEmpty()?-1:stack.peek();
int cur=height[j]*(i-k-1);
maxArea=Math.max(cur,maxArea);
}
stack.push(i);
}
while(!stack.isEmpty()){
int j=stack.pop();
int k=stack.isEmpty()?-1:stack.peek();
int cur=(height.length-k-1)*height[j];
maxArea=Math.max(cur,maxArea);
}
reuturn maxArea;
}
最大值值减去最小值小于或等于num的子数组的数量
题目描述
给定数组arr和整数num,共返回多少个数组子数组的满足如下情况:
max(arr[i...j])-min(arr[i..j])<=num
要求:
如果数组的长度为N,时间复杂度为O(N)的解法
思路解析
使用双端队列实现,qmax维护窗口arr[i…j]的最大值更新的结构,qmin维护窗口子数组arr[i..j]最小值更新结构,
两个基本的结论:
- 如果子数组arr[i…j]满足条件,那么arr[i..j]的子数组arrk…l都满足条件
- 反之arr[i…j]不满足条件,那么包含该数组arr[i..j]的子数组必然不满足条件。
具体实现步骤:
- 生成双端队列qmax,qmin.生成两个整型变量i,j表示子数组的arr[i..j]
- 令j不断向右移动位置,不断跟新qmax,qmin结构,保证qmax,qmin位置窗口的最大值与最小值,一旦出现arr[i…j]不满足条件,说明arr[i…j-1],arr[i..j-2]….arr[i…i]都是满足条件的
- 执行完步骤2,令i++;并对qmax,qmin进行调整,qmax,qmin变为arr[i+1…j]的最大值,最小值的更新结构
- 根据步骤2,3,得到以arr[0]….arr[N-1]作为第一个元素的子数组满足条件的数量分别是多少
public int getNum(int[] arr,int num){
if(arr==null||arr.length==0){
return 0;
}
LinkedList<Integer> qmin=new LinkedList<>();
LinkedList<Integer> qmax=new LinkedList<>();
int i=0,j=0;
int res=0;
while(i<arr。length){
while(j<arr.length){
while(!qmin.isEmpty()&&arr[j]<=arr[qmin.peekLast()]){
qmin.pollLast();
}
qmin.addLast(j);
while(!qmax.isEmpty()&&arr[j]>=arr[qmax.peekLast()]){
qmax.pollLast();
}
qmax.addLast(j);
if(arr[qmax.peekFirst()]-arr[qmin.peekFirst()]>num)
break;
j++;
}
if(qmin.peekFist()==i)
qmin.pollFirst();
if(qmax.peekFirst()==i)
qmax.pollFirst();
res+=j-i;
i++;
}
return res;
}