这与栈和队列的特性有关,众所周知,栈是满足“先进后出”的,而队列是满足“先进先出”的,所以聪明的人类就想到了用两个栈来实现队列,哈哈哈,聪明!!!
class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
//无参构造,初始化
public MyQueue() {
stackIn = new Stack<>();
stackOut = new Stack<>();
}
//队列实现push和栈一样
public void push(int x) {
stackIn.push(x);
}
//队列弹出需要先弹出先进栈的部分,所以先判断stackOut是否为空,不为空才能将stackIn逆序全放进去,直到stackIn
//也为空,说明全弹出来了,最后返回stackOut最上边的
public int pop() {
if(stackOut.isEmpty()) {
while(!stackIn.isEmpty()) {
stackOut.push(stackIn.pop());
}
}
return stackOut.pop();
}
//peek其实和pop差不多,也是得将stackOut全填满,之后out最上边的就是peek
public int peek() {
if(stackOut.isEmpty()) {
while(!stackIn.isEmpty()) {
stackOut.push(stackIn.pop());
}
}
return stackOut.peek();
}
//stackIn、stackOut都为空才是空
public boolean empty() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
}
基础知识参见文献一
思路1===》两个队列(一个存放,另一个辅助)实现栈
思路2===》一个队列(Deque)实现栈
//思路1:两个队列(一个存放,另一个辅助)实现栈
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
//初始化
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
//压栈。将x装入队列2 ===> 将队列1中的装入队列2 ===> 将队列2全复制给队列1,以便之后使用
public void push(int x) {
queue2.offer(x);
while(!queue1.isEmpty()) {
queue2.offer(queue1.poll());
}
Queue<Integer> tempQueue;
tempQueue = queue1;
queue1 = queue2;
queue2 = tempQueue;
}
//队列1的顺序即为栈中的顺序
public int pop() {
return queue1.poll();
}
//队列1的顺序即为栈中的顺序
public int top() {
return queue1.peek();
}
//队列1的顺序即为栈中的顺序
public boolean empty() {
return queue1.isEmpty();
}
}
//思路2:一个队列(Deque)实现栈
class MyStack {
Deque<Integer> deque;
//初始化
public MyStack() {
deque = new ArrayDeque<>();
}
//压栈
public void push(int x) {
deque.addLast(x);
}
//deque除了最后一个元素其余的重新入队列,返回并删除第一个
public int pop() {
int size = deque.size();
size--;
while(size-- > 0) {
deque.addLast(deque.pollFirst());
}
int res = deque.pollFirst();
return res;
}
//返回deque压入的最后一个元素
public int top() {
return deque.peekLast();
}
//返回deque是否为空
public boolean empty() {
return deque.isEmpty();
}
遇到这种成对出现的问题,也就是匹配的问题来说,使用栈解决非常合适!
匹配括号有三种情况:
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
Stack<Character> stack = new Stack<>();
char c;
for(int i = 0; i < s.length(); i++) {
c = s.charAt(i);
if(c == '(') {
stack.push(')');
} else if(c == '[') {
stack.push(']');
} else if(c == '{') {
stack.push('}');
//情况三:stack.isEmpty()表示右括号没有匹配的左括号了
//情况二:stack.peek() != c表示栈中没有匹配的字符了
} else if(stack.isEmpty() || stack.peek() != c) {
return false;
} else {
stack.pop();
}
}
//情况一:字符串已遍历完,但是栈不为空,说明左括号没有匹配的右括号了
return stack.isEmpty();
但写完这个之后,又发现别人的题解,虽然思路大同小异,但借鉴一下别人的也是一种学习,他人代码如下:
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
//碰到左括号,就把相应的右括号入栈
if (ch == '(') {
deque.push(')');
}else if (ch == '{') {
deque.push('}');
}else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {//如果是右括号判断是否和栈顶元素匹配
deque.pop();
}
}
//最后判断栈中元素是否匹配
return deque.isEmpty();
刚开始不是很理解,这就查呀查,找呀找,参照这篇文章可以一目了然,哈哈哈!!!
里边写道不论是栈stack还是队列queue,都可以用双端队列deque来实现,里边还介绍了他们的方法对比,这里就用到了deque用作栈来使用的时候,它的peek()方法与stack 的peek()方法一样了,都是从入栈的地方出栈。所以用deque.peek() != ch是没毛病的。
同样是匹配问题,可以把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。
class Solution {
public String removeDuplicates(String s) {
//双端队列实现栈
Deque<Character> deque = new ArrayDeque<>();
char c;
for(int i = 0; i < s.length(); i++) {
//取出每个字符
c = s.charAt(i);
//栈为空或者栈顶与字符不等,压入栈;否则,弹出
if(deque.isEmpty() || c != deque.peek()) {
deque.push(c);
} else {
deque.pop();
}
}
String str = "";
//拼接成字符串
while(!deque.isEmpty()) {
str = deque.pop() + str;
}
return str;
}
}
就凭借着逆波兰表达式的特点,参考下边这个文档
参见文档
逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList<>();
for(int i = 0; i < tokens.length; i++) {
if("+".equals(tokens[i])) {
stack.push(stack.pop() + stack.pop());
//减法需要特殊处理一下
} else if("-".equals(tokens[i])) {
stack.push(-stack.pop() + stack.pop());
} else if("*".equals(tokens[i])) {
stack.push(stack.pop() * stack.pop());
//除法需要特殊处理一下
} else if("/".equals(tokens[i])) {
int down = stack.pop();
int up = stack.pop();
stack.push(up / down);
} else {
//这里需要转换成Integer
stack.push(Integer.valueOf(tokens[i]));
}
}
return stack.pop();
}
}
自定义单调队列,完成从大到小的顺序,滑动窗口,弹出最大(最左),加入新的数,直到结束。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) {
return nums;
}
//定义一个存放最大值的数组,其大小 = 数组长度 - 窗长 + 步长
int[] arr = new int[nums.length - k + 1];
//site记录存放位置
int site = 0;
//创建对象
MyQueue queue = new MyQueue();
//先把前k个数放入自定义队列中
for(int i = 0; i < k; i++) {
queue.add(nums[i]);
}
//存放第一个窗口中的最大值,site自加
arr[site++] = queue.peek();
//移动窗口,弹出一个,进来一个,把最开头的数给arr,site自加
for(int i = k; i < nums.length; i++) {
queue.poll(nums[i - k]);
queue.add(nums[i]);
arr[site++] = queue.peek();
}
return arr;
}
}
//自定义一个单调队列
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
void poll(int value) {
//这里写成while()了,人家只要弹一次就好了,你给人家使劲弹,哈哈哈
if(!deque.isEmpty() && value == deque.peek()) {
deque.poll();
}
}
void add(int value) {
while(!deque.isEmpty() && value > deque.getLast()) {
deque.removeLast();
}
deque.add(value);
}
int peek() {
return deque.peek();
}
}
over!!!