文章目录
- 知识
- 练习
- [225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/)
- *[155. 最小栈](https://leetcode.cn/problems/min-stack/)
- [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses/)
- [150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/)
- [739. 每日温度](https://leetcode.cn/problems/daily-temperatures/)
- [496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/)
- [503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/)
- [剑指 Offer 31. 栈的压入、弹出序列](https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/)
知识
栈和队列都是比较简单的数据结构,这里就不做过多的解释,只解释对应到Java中的类(建议自行查看源码–极其友好)。
Stack类
Stack extends Vector
synchronized E peek() 取栈顶元素-------时间复杂度O(1)
E push(E item) 进栈-------不考虑扩容,时间复杂度O(1)
synchronized E pop() 出栈-------时间复杂度O(1)
boolean empty() 判空--------时间复杂度O(1)
还有继承Vector的size()、clear()方法
⚡️对于单步操作的方法,方法是不用加同步的
Queue接口
队列一般使用LinkedList类,LinkedList implements Deque, Deque extends Queue
查看
E peek() 仅获取元素----O(1)
E element() 仅获取元素----O(1) -----两者区别在于对空指针的处理不同-返回null或抛出异常
移除队头,Last结尾的用于移除队尾,如pollLast
E poll() 移除----O(1)
E remove() 移除----O(1) -----同上
添加到队尾,First结尾的添加到队头
boolean offer(E e) 入队,调用了add(E e)---O(1)
💡栈和队列其实操作不多,基本方法就查看、添加、删除。
练习
不难,别被哪个O(1)误导就行,O(1)给感觉就是不用循环。。。。。其实这里的O(1)是指能让操作尽量多的达到O(1)的时间复杂度,但中间可能存在超过O(1)的操作。
思路:把一个栈当成删除pop;一个栈当成添加push;删除的栈空的时候,就把添加栈的元素全部放到删除的栈
import java.util.*;
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack2.isEmpty()) {
//栈1pop 栈2push
while(!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
同类题目:232. 用栈实现队列
class MyQueue {
Stack<Integer> s1;
Stack<Integer> s2;
public MyQueue() {
s1 = new Stack<Integer>();
s2 = new Stack<Integer>();
}
public void push(int x) {
s2.push(x);
}
public int pop() {
//若s1为空,将s2的pop进s1
sameOperate();
return s1.pop();
}
public int peek() {
//若s1为空,将s2的pop进s1
sameOperate();
return s1.peek();
}
//相同代码提取
private void sameOperate(){
if(s1.empty()){
while(!s2.empty()){
s1.push(s2.pop());
}
}
}
public boolean empty() {
return s1.size()==0 && s2.size()==0;//此时需要两个做判断
}
}
225. 用队列实现栈
思路一:整两个队列,添加元素选择队列不为空的;删除/查看元素时,则将不为空的队列的元素依次移入另一个队列,并对最后一个元素进行特殊操作即可(删除则不入队,查看则入队)
💡这里一直保持q1为空,是为了减少if的判断,类似JVM垃圾回收复制算法,不交换版本
class MyStack {
//让q1一直为空
Queue<Integer> q1;
Queue<Integer> q2;
public MyStack() {
q1 = new LinkedList<>();
q2 = new LinkedList<>();
}
public void push(int x) {
q2.add(x);
}
public int pop() {
int size = q2.size() - 1;
while(size-- > 0) {
q1.add(q2.poll());
}
int last = q2.poll();
//保证q1一直为空
swap();
return last;
}
//交换两队列
private void swap() {
Queue<Integer> tmp = q2;
q2 = q1;
q1 = tmp;
}
public int top() {
int size = q2.size() - 1;
while(size-- > 0) {
q1.add(q2.poll());
}
int last = q2.poll();
//注意查看后需要入队
q1.add(last);
swap();
return last;
}
public boolean empty() {
return q1.isEmpty() && q2.isEmpty();
}
}
思路二:单个队列实现,在添加元素后,将其前面的所有元素依次出队并再次加入该队列(出队后再入队),也可以在删除和查看时操作,一样的。
class MyStack {
Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<Integer>();
}
public void push(int x) {
int l = queue.size();
queue.offer(x);
while(l-- > 0){
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.size()==0;
}
}
*155. 最小栈
思路:双栈,一个实现栈的基本操作;一个实现最小值,对于每个入栈元素,都判断当前其最小值是什么,如果当前位置的值比最小值小,则更新最小值,并将最小值入栈,例如2 4 1 3 那么其最小值栈为 2 2 1 1(每个元素的位置到栈底的最小值都是明确的)
class MinStack {
Stack<Integer> sk;
Stack<Integer> msk;
int min;
public MinStack() {
sk = new Stack<Integer>();
msk = new Stack<Integer>();
min = Integer.MAX_VALUE;
}
public void push(int x) {
sk.push(x);
if(x<min){
min=x;
}
msk.push(min);//每一元素所处的位置都有其对应的最小值
}
public void pop() {
sk.pop();
msk.pop();//此时也需要pop,因为此位置不存在元素了,则也就不存在此位置的最小值了
//更新最小值
min = msk.empty() ? Integer.MAX_VALUE : msk.peek();
}
public int top() {
return sk.peek();
}
public int getMin() {
return min;
}
}
20. 有效的括号
思路:遇到左括号([{
就入栈,遇到右括号}])
就出栈匹配对不对,需要注意单边的((
或))
的情况,会导致结果不对和空栈异常,其中栈大小超过一半未匹配直接false,可下可不下,只是优化作用,例如((((((((((((((()
class Solution {
public boolean isValid(String s) {
int l = s.length();
//奇数直接false
if((l & 1) == 1) return false;
//栈的长度不可能超过一半
l = l / 2;
//栈
Stack<Character> sk = new Stack<>();
for(char a : s.toCharArray()) {
if(sk.size() > l) {
//超过一半未匹配直接false
return false;
}
if(a == '(' || a == '{' || a =='[') {
sk.push(a);
} else {
if(sk.isEmpty()) {
//特殊情况"))"
return false;
}
char tmp = sk.pop();
if(tmp == '(') {
if(a != ')') return false;
} else if(tmp == '{') {
if(a != '}') return false;
} else {
if(a != ']') return false;
}
}
}
//特殊情况"(("
return sk.isEmpty();
}
}
150. 逆波兰表达式求值
思路:仔细观察求指过程,不难发现,计算的时机就是遇到运算符就将前面的两个元素进行计算,然后将结果放回去,继续往后。这个操作过程其实就是出栈和入栈的过程。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> sk = new Stack<>();
for(String token : tokens) {
switch(token) {
case "+" : {
sk.push(sk.pop() + sk.pop());
break;
}
case "-" : {
int a = sk.pop();
int b = sk.pop();
//减法和除法需要注意运算顺序
sk.push(b - a);
break;
}
case "*" : {
sk.push(sk.pop() * sk.pop());
break;
}
case "/" : {
int a = sk.pop();
int b = sk.pop();
sk.push(b / a);
break;
}
default : {
//数字直接入栈
sk.push(Integer.parseInt(token));
}
}
}
return sk.pop();
}
}
739. 每日温度
思路:两两比较,小的则赋值为1,大于则入栈等到小的时候在拿出来比较。注意栈存放的是元素的下标,有下标才能计算相差几天,有下标才能定位修改哪个位置的元素,不然pop出来的是个什么东西都不知道?这里直接修改了temperatures数组,你也可以自己新建一个。
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
Stack<Integer> sk = new Stack<>();
int l = temperatures.length - 1;
for(int i = 0; i < l; i++) {
if(temperatures[i] < temperatures[i+1]) {
//小于
temperatures[i] = 1;
//拿出来比较
while(!sk.isEmpty() && temperatures[sk.peek()] < temperatures[i+1]) {
int idx = sk.pop();
temperatures[idx] = i + 1 - idx;
}
} else {
sk.push(i);
}
}
//不存在升温的
while(!sk.isEmpty()) {
temperatures[sk.pop()] = 0;
}
//最后一位
temperatures[l] = 0;
return temperatures;
}
}
优化:力扣提交超过100%的哪个的答案是类似暴力法,但从后往前遍历,不知道为什么那么快。
496. 下一个更大元素 I
思路:类似每日温度,不过需要借助哈希表,也就是先求nums2每个元素的下一个更大数,然后再去求解nums1。题意容易让人误解,其实求的就是nums2每个元素的下一个更大数,只是从中抽取一些元素构成nums1,然后求nums1在nums2的最大数。栈的作用不变,两两比较,小的加入哈希表,大的暂存起来,直到下次小的时候才拿出来比较,类似每日温度,注意这里并不需要用下标,直接存放元素值更方便。
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int l2 = nums2.length - 1;
//存放nums2各个元素和对应的下一个更大元素
Map<Integer, Integer> map = new HashMap<>();
Stack<Integer> sk = new Stack<Integer>();
for(int i = 0; i < l2; i++) {
if(nums2[i] < nums2[i+1]) {
map.put(nums2[i], nums2[i+1]);
//拿出来做比较
while(!sk.isEmpty() && sk.peek() < nums2[i+1]) {
map.put(sk.pop(), nums2[i+1]);
}
} else {
sk.push(nums2[i]);
}
}
while(!sk.isEmpty()) {
map.put(sk.pop(),-1);
}
map.put(nums2[l2], -1);
//此时map已经获得了nums2每个元素的下一个更大数了
int l1 = nums1.length;
for(int i = 0; i < l1; i++) {
//修改到nums1中即可
nums1[i] = map.get(nums1[i]);
}
return nums1;
}
}
503. 下一个更大元素 II
思路:上一题的扩展,求解的数组变为循环数组,可以遍历一次之后,再对栈中剩余的元素进行遍历(前面的题目是赋值-1,但这里还需要跟前面的比较一次,因为数组可循环)。注意这里的栈用于存放下标。除此之外不能修改原数组,因为第二次遍历需要用到前面的数据。
测试用例
[1,5,3,6,8]
[1,2,3,4,3]
[1,2,3,2,1]
[5,4,3,2,1]
[100,1,11,1,120,111,123,1,-1,-100]
class Solution {
public int[] nextGreaterElements(int[] nums) {
int[] result = new int[nums.length];
//初始化,也可以不要,但最后面栈剩余元素需要使用while将其填充为-1
Arrays.fill(result,-1);
int l = nums.length - 1;
Stack<Integer> sk = new Stack<>();
//第一次比较
for(int i=0;i<l;i++){
if(nums[i] < nums[i+1]) {
result[i] = nums[i+1];
while(!sk.empty() && nums[sk.peek()]<nums[i+1]){
result[sk.pop()]=nums[i+1];
}
} else {
sk.push(i);
}
}
sk.push(l); //最后一个
//第二次比较
int j=0;
while(!sk.empty() && j<=l){
if(nums[sk.peek()] < nums[j]){
result[sk.pop()]=nums[j];
}else{
j++;
}
}
return result;
}
}