一、括号匹配问题
本题主要是有关括号顺序类的题,有关顺序的题都可以考虑用栈来做。
思路:遍历存储括号们的字符串,新出现的右括号一定与最新入栈的左括号匹配,所以当碰见左括号就进行入栈操作,当出现右括号进行出栈操作即可。
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length() ; i++) {
char ch = s.charAt(i);
if(ch == '{' || ch == '[' || ch == '(' ){ //为左括号时入栈
stack.push(ch);
}else {
if(stack.empty()){ //当栈空但字符串出现右括号 说明不匹配
return false;
}
char ch2 = stack.peek(); //获取栈顶左括号
if(ch2 == '{' && ch == '}' || ch2 == '[' && ch == ']' || ch2 == '(' && ch == ')' ){
stack.pop(); //当左右括号匹配时 出栈
} else{
return false; //不匹配时 return
}
}
}
return stack.empty()? true : false; //当字符串遍历完而栈内还有元素则说明不匹配
}
}
不匹配的情况:
1.当栈为空但遍历到右括号时 false
2.当右括号与新入栈的左括号不匹配时 false
3.当字符串遍历完但栈不为空 false
二、逆波兰表达式求值
当给出一段字符串求逆波兰表达式,思路:遍历字符串,为数字时入栈,为符号时出栈两个数字,先出栈的放在符号右边,计算完成后入栈,重复操作。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
//因为栈内存储的一定为数字 所以创建Integer泛型栈
for(int i = 0;i < tokens.length;i ++){ 遍历字符串tokens
String str = tokens[i];
if( !Operation(str)){ 如果不是操作符
int val = Integer.valueof(str); //将str转换为int类型后入栈
stack.push(val);
}else{ //当不是数字时
int num1 = stack.pop(); //出栈两个数字
int num2 = stack.pop();
switch(str){ //因为分为四种情况,使用switch便于操作
case "+":
stack.push(num2 + num1);
break;
case "-":
stack.push(num2 - num1);
break;
case "*":
stack.push(num2 * num1);
break;
case "/":
stack.push(num2 / num1);
break;
}
}
}
return stack.pop(); //遍历完字符串 此时栈内的元素则为结果
}
public boolean Operation(String str){ //判断是否为操作符
if(str.equals('-') || str.equals('+') || str.equals('*') || str.equals('/')){
return true;
}
return false;
}
}
1.栈内存储的一定为数字 所以创建Integer泛型栈
2.当遍历的不是操作符,转换为int类型后入栈
3.四种操作符所以有四种情况,依次列举即可
整体来说不算很难,知道思路后慢慢写就好。
三、判断第二个数组是否为第一个数组的压入弹出序列
因为栈是先进后出的单向列表,经常会有判断一个数组是否为另一个数组的合法弹出序列的题目,通过本题可以透彻学会此类题型。
思路:遍历两个数组,首先遍历push数组然后与pop数组计数器j位置的元素比较,如果相同就出栈,计数器向后移动。不同就入栈直到碰到相同元素。
public boolean IsPopOrder (int[] pushV, int[] popV) {
Stack<Intager> stack = new Stack<>(); //创建Integer泛型栈
int j = 0; //j为遍历popV数组的计数器
for(int i = 0;i < pushV.length;i++){ //首先遍历push数组
if(pushV[i] != popV[j] && j < popV.length ){//当碰见与pop数组不相同的元素就入栈
stack.push(pushV[i]);
}else{ //相同则出栈
stack.pop();
j++; //j移到pop数组下一位置
}
}
//当pop数组未遍历完成,但栈已经为空,则说明不符合,return false
if(j <= popV.length || !stack.isEmpty()){
return false;
}
return true;
}
}
1.需要注意j的值不能超过数组长度,否则会出现栈溢出现象
2.当pop数组未遍历完成,但栈已经为空,则说明不符合
四、获取栈中最小值
思路:栈只能进行单向操作先入后出,要想获得栈中的最小值需要建一个新栈来存储最小值。最小值栈入栈时应该进行判断,如果小于栈顶的元素才可以入栈,入栈后此元素就为最小值,这样的话将最小值栈出栈一个元素后栈顶的元素就是新的最小值。
public MinStack() {
stack = new Stack<>(); //普通栈
minstack = new Stack<>(); //最小值栈
}
public void push(int val) {
stack.push(val); //普通栈直接入栈元素
if(minstack.isEmpty()){ //先判断最小值栈是否为空
minstack.push(val); //为空则入栈
return;
}
if(minstack.peek() <= val){ //判断元素与栈顶的大小
stack.push(val);
return;
}else{
return;
}
}
public void pop() {
int num = stack.pop();
if(num == minstack.peek()){ //如果普通栈出栈的元素与最小值栈顶元素相同
minstack.pop(); //则最小值也出栈
}
}
public int top() {
return stack.peek(); //获取栈顶元素
}
public int getMin() {
return minstack.peek(); //获取最小值
}
1.当最小值栈为空时直接压入元素
2.当普通栈出栈一个与最小值栈相同的元素,最小值栈也应该出栈,否则会出现最小值是一个不存在的数字
3.如果新的元素与最小值栈的相同也需入栈,否则普通栈有两个这个数,当出栈一个后最小值栈也会把这个数出栈,如果这个数为最小值的话那就会出现数据丢失。
五、设计循环队列
思路:设置front,rare,usedsize为成员变量
int[] elem; //为初始队列
int front; //头部
int rare; //尾部
int usedSize; //实际占用元素
1.构造方法:将数组初始化为给定的形参大小
public MyCircularQueue(int k) {
elem = new int[k];
}
2.Front 获取头部元素 直接return头部下标元素
public int Front() {
if(isEmpty()){ //当栈空返回-1
return -1;
}
return elem[front];
}
3.Rear获取队尾元素
public int Rear() {
if(isEmpty()){
return -1;
}
return elem[(rare-1 + elem.length) % elem.length];
}
4.插入元素
public boolean enQueue(int value) {
if(isFull()){
return false;
}
elem[rare] = value;
rare ++;
if(rare == elem.length){
rare = 0;
}
usedSize ++;
return true;
}
5.删除元素,在头部删除元素时,front可能会在尾部的位置,直接front++会栈溢出,借助下面公式就能帮助回到初始位置。
public boolean deQueue() {
if(isEmpty()){
return false;
}
front ++;
front = (front+elem.length) % elem.length;
usedSize --;
return true;
}
6.判断队列为空还是满,直接借助usedSize就能获得
public boolean isEmpty() {
return usedSize == 0;
}
public boolean isFull() {
return usedSize == elem.length;
}