栈与队列
1.理论基础
stack:先进后出
queue:先进先出
232 用栈实现队列
栈是先进后出,队列是先进先出。
所以我们可以用两个栈来表示队列。一个栈表示进,另一个栈表示出
class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
public MyQueue() {
stackIn = new Stack<>();//负责进栈
stackOut = new Stack<>();//负责出栈
}
public void push(int x) {
stackIn.push(x);
}
public int pop() {
dumpstackIn();
return stackOut.pop();
}
public int peek() {
dumpstackIn();
return stackOut.peek();
}
public boolean empty() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
private void dumpstackIn(){
//如果栈out还没有出完,那么就不能往栈out里放元素了。
if(!stackOut.isEmpty()) return;
while(!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
225. 用队列实现栈
在Java中,Queue是一个接口,底层是通过链表实现的。
Deque:双端队列,指允许两端都可以进行入队和出队操作的队列,意为double ended queue。 其通过ArrayDeque/LinkedList初始化实现.
ArrayDeque和LinkedList这两者底层,一个采用数组存储,一个采用链表存储;
数组存储,容量不够时需要扩容和数组拷贝,通常容量不会填满,会有空间浪费;
链表存储,每次push都需要new Node节点,并且node节点里面有prev和next成员,也会有额外的空间占用。
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
queue2.offer(x);//先放在辅助队列中
while(!queue1.isEmpty()){
queue2.offer(queue1.poll());
}
Queue<Integer> queueTemp;
queueTemp = queue1;
queue1 = queue2;
queue2 = queueTemp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
public boolean empty() {
return queue1.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
20 有效的括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == '('){
deque.push(')');
}else if(s.charAt(i) == '{'){
deque.push('}');
}else if(s.charAt(i) == '['){
deque.push(']');
}else if(deque.isEmpty() || deque.pop() != s.charAt(i)){
return false;
}
}
return deque.isEmpty();
}
}
1047 删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
解析:可以采用deque,但是最后在返回结果的时候要注意顺序,
class Solution {
public String removeDuplicates(String s) {
ArrayDeque<Character> deque = new ArrayDeque<>();
for(char ch : s.toCharArray()){
if(deque.isEmpty()){
deque.push(ch);
}else if(deque.peek() == ch){
deque.pop();
}else{
deque.push(ch);
}
}
String res = "";
//注意
while(!deque.isEmpty()){
res = deque.pop() + res;
}
return res;
}
}
150. 逆波兰表达式
首先,我们先明确什么是逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。
平常使用的算式则是一种中缀表达式,如(1 + 2) * (3 + 4)
该算式的逆波兰表达式写法为:((1 2 +)(3 4 +)*)
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList<>();
int res = 0;
for(String s : tokens){
if("+".equals(s)){
stack.push(stack.pop() + stack.pop());//leetcode这里报错,
}else if("-".equals(s)){
res = stack.pop() - stack.pop();
stack.push(res);
}else if("*".equals(s)){
res = stack.pop() * stack.pop();
stack.push(res);
}else if("/".equals(s)){
int temp = stack.pop();
res = stack.pop() / temp;
}else{
stack.push(Integer.valueOf(s));
}
}
return res;
}
}
239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 1){
return nums;
}
int len = nums.length - k + 1;
//存放结果元素的数组
int[] res = new int[len];
int num = 0;
//自定义队列
MyQueue myQueue = new MyQueue();
//先将前k的元素放入队列
//将最大的元素放在最前面,保持单调递减
for(int i = 0; i < k ; i++){
myQueue.add(nums[i]);
}
res[num++] = myQueue.peek();
for(int i = k; i < nums.length; i++){
//滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
myQueue.poll(nums[i - k]);
//滑动窗口加入最后面的元素
myQueue.add(nums[i]);
//记录对应的最大值
res[num++] = myQueue.peek();
}
return res;
}
}
class MyQueue{
Deque<Integer> deque = new LinkedList<>();
//当弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
void poll(int val){
if(!deque.isEmpty() && val == deque.peek()){
deque.poll();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将入队,比1大,所以1弹出,此时队列3,2
void add(int val){
while(!deque.isEmpty() && val > deque.getLast()){
deque.removeLast();
}
deque.add(val);
}
//队列队顶元素始终为最大值
int peek(){
return deque.peek();
}
}
347. 前k个高频元素
/**Comoarator接口说明
返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
对于队列:排在前面意味着往队头里靠
对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆)
从队头到队尾按从大到小排就是最大堆(大顶堆)-->队头元素相当于对堆的根结点
*/
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//基于大顶堆实现
Map<Integer, Integer> map = new HashMap<>();
for(int num : nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}
//在优先队列中存储二元组(num, cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从大到小排,出现次数最多的是在队头(相当于大顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1] - pair1[1]);
for(Map.Entry<Integer, Integer> entry : map.entrySet()){//大顶堆需要对所有的元素进行排序
pq.add(new int[] {entry.getKey(), entry.getValue()});
}
int[] ans = new int[k];
for(int i = 0; i < k; i++){
ans[i] = pq.poll()[0];
}
return ans;
}
}