文章目录
🍁栈与队列的解题关键
1、熟悉栈与队列的特点,栈->先进后出、队列->先进先出
2、知道Java中栈与队列是用什么数据结构表示的
i.栈一般是用是Deque接口
ii.队列是用Queue接口
🍁一、有效的括号
20.有效的括号
1、本题的思路主要在于利用栈的性质,来匹配是否有括号对应
2、因为再依次遍历括号字符串的过程中,先遇到的括号,一定是在最后才能匹配
3、同时利用一些操作,采用匹配到左括号,放入右括号的操作
4、在判断中注意三种情况都是不满足匹配上的
i.当栈为空,说明左边括号少,右边多了
ii.当栈最后不为空,说明左边括号多,右边少了
iii.匹配过程中,与栈顶元素不一致了
public class L20 {
public static void main(String[] args) {
String s = "([]))";
System.out.println(isValid(s));
}
//栈实现
//遇到左括号放入右括号
//遇到右括号弹出栈顶元素,看是否相等
//如果当前栈已经为空,说明右括号多了,返回false
//如果与当前元素与栈顶不一样,说明也没有匹配上,返回false
public static boolean isValid(String s) {
Deque<Character> myque = new LinkedList<>();
for (int i = 0; i < s.length(); i++) {
Character c = s.charAt(i);
if (c.equals('[')) {
myque.push(']');
} else if (c.equals('(')) {
myque.push(')');
} else if (c.equals('{')) {
myque.push('}');
} else if (myque.isEmpty() || !c.equals(myque.remove())) {
return false;
}
}
return myque.isEmpty();
}
}
🍁二、最小栈
155.最小栈
1、本题的难点在于,如何维护一个最小值的数字,能够快速检索最小值
2、利用栈的特性,维护两个栈,一个是所有数字的栈,一个是最小值栈
3、难点在于如何入栈,每次入栈的时候,和最小值栈栈顶比较,如果入栈的值比最小的小,则最小值入新的值,否则加入原先的栈顶元素即可
public class L155 {
static class MinStack {
// 数值栈
Deque<Integer> xStack;
// 最小值栈
Deque<Integer> minStack;
public MinStack() {
//初始化
xStack = new LinkedList<Integer>();
minStack = new LinkedList<Integer>();
minStack.push(Integer.MAX_VALUE);
}
// 每次push时,将当前值与最小值栈的栈顶元素比较,将较小的值压入最小值栈
// 这样,最小值栈的栈顶元素始终是当前所有元素中的最小值
// 因为如果当前这个最小值没有弹出,上面的值一定比他大
public void push(int val) {
xStack.push(val);
Integer myTop = minStack.peek();
if(val < myTop){
minStack.push(val);
}else{
minStack.push(myTop);
}
}
public void pop() {
xStack.pop();
minStack.pop();
}
public int top() {
return xStack.peek();
}
// 能够直接拿到最小值
public int getMin() {
return minStack.peek();
}
}
public static void main(String[] args) {
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.push(-4);
System.out.println(minStack.getMin());
minStack.pop();
minStack.top();
System.out.println(minStack.getMin());
}
🍁三、用栈实现队列
232.用栈实现队列
1、本题用两个栈实现队列,关键在于如何把先进后出转成先进先出
2、很容易想到只要每次出栈的时候把原本一个栈的数据放入到另一个栈就能实现反转
3、可以优化的地方在于,出栈的时候,如果出栈的栈还没有空,可以直接出,如果出栈的栈空了,再把入栈的栈的数据放入到出栈的栈中
public class L232 {
static class MyQueue {
Deque<Integer> inStack;
Deque<Integer> outStack;
//两个栈实现队列
//一个栈用来入栈,一个栈用来出栈
//关键在于出栈的时候,只要out为空,才重新压入in
//否则可以直接出栈,因为一旦压入后,一定会保存原先先进的
public MyQueue() {
inStack = new LinkedList<Integer>();
outStack = new LinkedList<Integer>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if(outStack.isEmpty()){
while(!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
public int peek() {
if(outStack.isEmpty()){
while(!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
public static void main(String[] args) {
MyQueue myQueue = new MyQueue();
myQueue.push(1);
myQueue.push(2);
System.out.println(myQueue.peek());
System.out.println(myQueue.pop());
System.out.println(myQueue.empty());
}
}
🍁四、字符串解码
394.字符串解码
1、本题分析在于因为是一层层嵌套打印的,最里面的数据是最先打印的,因此考虑用栈作为数据结构
2、因为同时会有计数的数量显示,因此还需要一个计数的栈,这样打印的时候可以知道弹出的字符串栈要打印多少次
3、这里的具体字符串拼接,我是这么理解的,弹出的是拼在前面的某个字符串,而循环的是当前已经拼接的res字符串
4、遍历字符串,要区分不同的情况,对于数字要用*10累加法,其他在遇到[ 号时要把当前存储的数量和字符串先清空
public class L394 {
static class Solution {
public String decodeString(String s) {
StringBuilder res = new StringBuilder();
int multi = 0;
// 倍乘 栈
// 记录操作
Deque<Integer> stack_multi = new LinkedList<>();
Deque<String> stack_res = new LinkedList<>();
// 遍历这个字符串
for(Character c : s.toCharArray()){
//遇到左括号,说明前面已经有数字记录下来多少次
//且已经记录前一条记录的字符串
//放入栈中
if(c == '['){
stack_multi.push(multi);
stack_res.push(res.toString());
// 置零 置空
multi = 0;
res = new StringBuilder();
}else if(c == ']'){
//遇到右括号,说明要出栈了
// 当前的res就是说明要循环的字符的数量
// 出栈,拼接字符串
// 循环次数
//最后拼到res上,要弹出栈顶
StringBuilder tmp = new StringBuilder();
int cur_multi = stack_multi.pop();
for(int i = 0; i < cur_multi; i++){
tmp.append(res);
}
res= new StringBuilder(stack_res.pop() + tmp);
}
//计算数字,有多少次
else if(c >= '0' && c <= '9'){
multi = multi * 10 + Integer.parseInt(c + "");
}
// 遇到字母先拼接上,代表跟前面数字匹配的重复的数量
else {
res.append(c);
}
}
return res.toString();
}
}
public static void main(String[] args) {
Solution solution = new Solution();
String s = "3[a]2[bc]";
System.out.println(solution.decodeString(s));
}
}
🍁五、数据流的中位数
1、这一题非常的巧妙,排查一组数据中的中位数
2、很容易想到能够用二叉搜索树来构建数据结构
3、但二叉搜索树没有现成的数据结构
4、选用大顶堆和小顶堆来考虑问题,把数据分割成左右子树,左边是小的半边数据,右边是大的半边数据
5、大顶堆相当于存放的是小的半边,小顶堆存放大的半边,这样可以实现左边的最大值,也就是大顶堆的peek,比右边的最小值,也就是小顶堆的peek要小,这样才完成分割左右数据的效果
5、最后返回值要考虑奇偶性,奇数就返回左边的peek,偶数返回两者的平均值
295.数据流的中位数
下面两个大佬的评论帮助更好理解需求以及实现的代码
代码的理解
public class L295 {
static class MedianFinder {
//最关键要弄清楚
//A是小顶堆,每次会返回最小值提供给B
//B是大顶堆,每次返回最小值给A
//因为希望是A的最小值 要比 B的最大值要大
//所以设置这样的数据结构
//循环倒
//不相等 去 B 导到 A
//因为最后取中位数如果长度不一样是去取A的peek
Queue<Integer> A, B;
public MedianFinder() {
A = new PriorityQueue<>();
B = new PriorityQueue<>((a, b)-> (b-a));
}
public void addNum(int num) {
//长度不一致,
if(A.size() != B.size()){
A.add(num);
B.add(A.poll());
}else{
//长度一致,是刚开始都为空的情况
//最后的结果是把数据先放到A,B只是中转服务
B.add(num);
A.add(B.poll());
}
}
public double findMedian() {
return A.size() != B.size() ? A.peek() :(A.peek() + B.peek()) / 2.0;
}
}
public static void main(String[] args) {
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
System.out.println(medianFinder.findMedian()); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
System.out.println(medianFinder.findMedian()); // return 2.0
}
}