今天的练习是栈的相关题目,首先先解释一下什么是栈
栈的基本思路是通过数组来存储栈中的元素,并通过栈顶指针指示栈顶元素在数组中的位置。
栈具有以下特点:
存储结构:使用数组作为底层存储结构,数组的每个元素存储栈中的一个元素;
操作受限:栈只能从栈顶插入和删除元素,不支持在栈中间插入和删除元素;
先进后出:栈的元素遵循 "先进后出" 的原则,即后插入的元素先被删除;
顺序访问:只能从栈顶开始访问栈中的元素,不能从栈底或中间位置访问元素。
在开始栈相关的算法前,我们需要一个栈,那么先来手写一个栈结构
手写栈:
首先就是基础的属性:栈内所存的元素个数,栈的初始化,栈的初始化长度
java
//1.定义一个数组
private int[] array;
//2.定义栈的元素个数
private int stackSize = 0;
//3.定义栈的默认容量
private final int CAPACITY_STACK = 5;
public MyStack() {
this.array = new int[CAPACITY_STACK];
}
入栈:
入栈的逻辑:元素的个数就是每个元素在数组中对应的索引位,当有元素入栈时,本质上就是按照当前的顺序,放入到对应的索引位上
需要注意的就是栈的扩容,因为底层还是数组,所以需要考虑溢出的情况,当元素的个数与数组的长度相等时,证明栈已经满了,我们需要进行扩容,实际的操作就是,将数组的内容拷贝到一个新的更大的数组里
代码如下:
java
public int push(int e){
//放入元素后,判断当前元素个数是否已经溢出,如果溢出则进行数组的扩容
if (isFull()){
array = Arrays.copyOf(array, array.length * 2);
}
array[stackSize] = e;
stackSize++;
return e;
} //将e入栈,并返回e
public boolean isFull() { //判断数组是否满了的方法
return stackSize == array.length;
}
出栈:
出栈的逻辑也很简单,栈的特点是先进后出,所以从数组的尾部进行弹出即可,还是根据元素个数去进行索引的对应
出栈考虑的事情就是,如果数组是空的,可能会出现索引越位的问题,那么就需要进行判空处理
代码如下:
java
public int pop(){
//将stackSize当前位置的元素变为null,然后stackSize--,返回当前位置元素
if (empty()) {
System.out.println("当前栈为空");
return -1;
}
int result = array[stackSize - 1]; // 从栈顶位置取出元素
array[stackSize - 1] = 0; // 清除栈顶元素(可选)
stackSize--;
return result;
} //将栈顶元素出栈并返回
public boolean empty(){
return stackSize == 0;
} //检测栈是否为空
到这里,差不多就好了,下面开始栈相关的算法题
1.有效的括号
链接:有效的括号
示例 :
输入:s = "()[]{}"
输出:true
输入:s = "(]"
输出:false
这个题目的目标是,需要对应的括号匹配,对于这种匹配问题,栈是很好用的
思路:
括号被分为左括号和右括号,那么当匹配到左括号时,我们将对应的右括号入栈,当匹配到右括号时,进行出栈,如果相等,则证明相匹配,正常的弹出,如果不匹配或者栈内没有对应匹配的括号,可以直接返回false
这时还会出现一种情况: (()
如果我们按照上面的方式进行入栈出栈,最后栈中还剩下一个右括号,大家可以想一想,那么这种情况也是错误的,所以我们还需要多加一个判断,就是栈必须为空
代码如下:
java
import java.util.Arrays;
public class EffectiveParentheses {
public class MyStack {
/*
通过数组实现栈
*/
//1.定义一个数组
private int[] array;
//2.定义栈的元素个数
private int stackSize = 0;
//3.定义栈的默认容量
private final int CAPACITY_STACK = 5;
public MyStack() {
this.array = new int[CAPACITY_STACK];
}
public int push(int e) {
//放入元素后,判断当前元素个数是否已经溢出,如果溢出则进行数组的扩容
if (isFull()) {
array = Arrays.copyOf(array, array.length * 2);
}
array[stackSize] = e;
stackSize++;
return e;
} //将e入栈,并返回e
public boolean isFull() { //判断数组是否满了的方法
return stackSize == array.length;
}
public int pop() {
//将stackSize当前位置的元素变为null,然后stackSize--,返回当前位置元素
if (empty()) {
System.out.println("当前栈为空");
return -1;
}
int result = array[stackSize - 1]; // 从栈顶位置取出元素
array[stackSize - 1] = 0; // 清除栈顶元素(可选)
stackSize--;
return result;
} //将栈顶元素出栈并返回
public int peek() {
if (empty()) {
System.out.println("当前栈为空");
}
return array[stackSize - 1];
} //获取栈顶元素,栈顶元素不出栈
public int size() {
return stackSize;
} //获取栈中有效元素个数
public boolean empty() {
return stackSize == 0;
} //检测栈是否为空
}
/*
有效的括号
*/
public boolean isValid(String s) {
/*
思路:首先遍历整个字符串,如果遍历到{ ( [ ,则压入栈中,如果再遍历到} ) ]则弹出栈的头元素
如果相等,则有效,继续遍历判断,否则直接返回false
*/
char[] charArray = s.toCharArray();
MyStack stack = new MyStack();
for (char str : charArray){
if (str == '('){
stack.push(')');
} else if (str == '{') {
stack.push('}');
} else if (str == '[') {
stack.push(']');
}
if (str == ')' || str == '}' || str == ']') {
int pop = stack.pop();
if (pop != str || pop == -1){
return false;
}
}
}
return stack.empty();
}
}
2.最小栈
示例 :
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
这道题实际上就是为栈内的元素,在不改变原数组的情况下进行排序,本质上就是为数组进行排序 ,那么就比较简单了
思路:
(1)新建一个数组,将栈内的数组深拷贝到新数组中
(2)为数组进行排序
(3)返回数组的第一个值,为最小值
代码如下:
java
import java.util.Arrays;
public class MinStack {
/*
通过数组实现栈
*/
//1.定义一个数组
private int[] array;
//2.定义栈的元素个数
private int stackSize = 0;
//3.定义栈的默认容量
private final int CAPACITY_STACK = 5;
public MinStack() {
this.array = new int[CAPACITY_STACK];
}
public int push(int e){
//放入元素后,判断当前元素个数是否已经溢出,如果溢出则进行数组的扩容
if (isFull()){
array = Arrays.copyOf(array, array.length * 2);
}
array[stackSize] = e;
stackSize++;
return e;
} //将e入栈,并返回e
public boolean isFull() { //判断数组是否满了的方法
return stackSize == array.length;
}
public int pop(){
//将stackSize当前位置的元素变为null,然后stackSize--,返回当前位置元素
if (empty()) {
System.out.println("当前栈为空");
}
int result = array[stackSize - 1]; // 从栈顶位置取出元素
array[stackSize - 1] = 0; // 清除栈顶元素(可选)
stackSize--;
return result;
} //将栈顶元素出栈并返回
public int top(){
if (empty()) {
System.out.println("当前栈为空");
}
return array[stackSize -1];
} //获取栈顶元素,栈顶元素不出栈
public int size(){
return stackSize;
} //获取栈中有效元素个数
public boolean empty(){
return stackSize == 0;
} //检测栈是否为空
public int getMin() {
if (empty()) {
System.out.println("栈为空");
return -1; // 返回一个无效值或抛出异常
}
// 深拷贝原数组到新数组
int[] copyArray = Arrays.copyOf(array, stackSize);
// 对新数组进行排序
Arrays.sort(copyArray);
// 返回排序后的第一个元素,即最小值
return copyArray[0];
}
}
3.字符串解码
链接:字符串解码
示例 1:
输入: s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入: s = "3[a2[c]]"
输出:"accaccacc"
这题很有难度,看起来不难,但是写起来总是被绕进去,第一个问题就是,如何保存对应括号内的元素的重复次数,第二个问题,如何处理嵌套,也就是示例2的情况。
首先需要确认区间问题,也就是怎么分组,将 **数字 + [ 元素 ]**作为一个整体,先不考虑嵌套问题,我们就需要将每个区间的数字保存起来
也就是说,当出现 ] 时 ,就需要进行整个区间的元素合并,数字 * 元素并与处理好的字符串进行拼接,那么大体的思路是这样,难度是我们该如何实现和处理嵌套问题
这里就需要使用栈,我的思路是使用双栈
思路:
第一个栈用于保存每个区间的出现次数,第二个栈用于保存处理好的字符串。
StringBuilder代表正在处理的字符串
当遍历到数字时,先记录下来,当遍历到左括号时,证明区间开始,将本次区间的出现次数和处理过的元素保存到对应的栈中(先进后出)
为了处理嵌套问题,我们需要将变量初始化,避免出现错误
当遍历到元素时,正常的添加到StringBuilder中
最后遍历到右括号,也就是区间结束时,证明这次的元素遍历结束,取出本次区间的出现次数,和之前处理好的字符串,将记录下来的StringBuiler与其进行拼接,这时StringBuilder存储的就是最终结果
代码如下:
java
import java.util.Stack;
public class StringDecoding {
/*
字符串解码
*/
public String decodeString(String s) {
//存储出现次数
Stack<Integer> countStack = new Stack<>();
//存储已经处理好的字符串
Stack<StringBuilder> stringStack = new Stack<>();
//正在处理的字符串
StringBuilder currentString = new StringBuilder();
//出现数字
int currentNumber = 0;
// 2[ab2[c]] --> abccabcc
// ab2[c] --> abcc
for (char c : s.toCharArray()) {
if (c >= '0' && c <= '9'){
currentNumber = currentNumber * 10 + (c - '0');
} else if (c == '[') {
//进入第一个区间
//保存已经处理好的字符串
//保存该区间的重复次数
stringStack.push(currentString);
countStack.push(currentNumber);
//可能还有嵌套,需要重复次数清0,字符串同理
currentNumber = 0;
currentString = new StringBuilder();
} else if (c == ']') {
//证明区间已经结束,取出该区间的重复值
Integer integer = countStack.pop();
//取出正在处理,也就是区间的字符串
StringBuilder stringBuilder = currentString;
//取出保存好的字符串,进行拼装
currentString = stringStack.pop();
for (int i = 0; i < integer; i++) {
currentString.append(stringBuilder);
}
}else {
//正常添加
currentString.append(c);
}
}
return currentString.toString();
}
}
4.每日温度
链接:每日温度
这道题也是看别的大佬做出来的哈哈哈,他讲的很清晰,大家有兴趣可以去看一下这篇文章吧
【Java图解算法】739.每日温度_每日温度 算法 java-CSDN博客
今天的练习就到这里了!