前言
本博客是对队列与栈做相关练习时所做笔记,主要内容来源队列与栈。
队列
普通队列
-
特点:先入先出。从队尾入队,从队首出队。
-
实现:可使用动态数组来实现,并用一个指针指向数组的头部,即记录头部的位置。入队会向队列追加一个新元素,而出队会删除第一个元素。
class MyQueue { // 动态数组存储元素 private List<Integer> data; // 用一个指针指向头部 private int p_start; public MyQueue() { data = new ArrayList<Integer>(); p_start = 0; } //入队,插入成功返回true public boolean enQueue(int x) { data.add(x); return true; }; //出队,同时指向头部的指针后移一位 public boolean deQueue() { if (isEmpty() == true) { return false; } p_start++; return true; } //获取队首元素 public int Front() { return data.get(p_start); } //判断队列是否为空 public boolean isEmpty() { return p_start >= data.size(); } };
可见,上述操作虽然实现简单,但随着头部指针的移动,将会浪费大量的空间。而循环队列可以解决这个问题。
循环队列
-
循环队列使用固定大小的数组,并使用一个指针(设为
head
)指向队首,一个指针(设为tail
)指向队尾元素的下一个位置。 -
入队时移动
tail
,即tail = (tail+1) % queue.length;
出队时移动
head
,即head = (head+1) % queue.length;
-
队列空时:
head == tail;
队列满时:
(tail+1) % queue.length == head;
622.设计循环队列
-
题目描述:设计一个循环队列,其支持以下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。 -
分析:
使用一个指针指向队首,一个指针指向队尾元素的下一个位置。这样做可更加容易实现一些操作。
-
代码:
class MyCircularQueue { private int[] queue; //head指向头部,tail指向队尾元素的下一个位置 private int head, tail; //初始化一个长度为k的队列 public MyCircularQueue(int k) { queue = new int[k+1]; head = 0; tail = 0; } /** Insert an element into the circular queue. Return true if the operation is successful. */ public boolean enQueue(int value) { if(isFull()) return false; queue[tail] = value; tail = (tail+1) % queue.length; return true; } /** Delete an element from the circular queue. Return true if the operation is successful. */ public boolean deQueue() { if(isEmpty()) return false; head = (head+1) % queue.length; return true; } /** Get the front item from the queue. */ public int Front() { if(isEmpty()) { return -1; } return queue[head]; } /** Get the last item from the queue. */ public int Rear() { if(isEmpty()) { return -1; } return queue[(tail-1+queue.length) % queue.length]; } /** Checks whether the circular queue is empty or not. */ public boolean isEmpty() { if(head == tail) return true; return false; } /** Checks whether the circular queue is full or not. */ public boolean isFull() { if((tail+1) % queue.length == head) { return true; } return false; } }
Java内置队列库
public class Test {
public static void main(String[] args) {
//创建一个队列
Queue<Integer> q = new LinkedList<>();
//判断队列是否为空
System.out.println(q.isEmpty());
//入队
q.offer(5);
q.offer(4);
q.offer(6);
//出队
int x = q.poll();
System.out.println(x);
//查看队首元素,为空返回null
int element = q.peek();
System.out.println(element);
//队列大小
System.out.println(q.size());
}
}
栈
栈的简单实现
-
与队列不同的是,栈是一种**后入先出(LIFO)**的数据结构。
-
通常,插入操作在栈中被称作入栈push,删除操作被称为出栈pop,入栈和出栈都是针对于栈中的最后一个元素。
-
栈的简单实现
class MyStack { //使用动态数组来存储元素 private List<Integer> data; public MyStack() { data = new ArrayList<>(); } /** 入栈,向数组末尾添加元素 */ public void push(int x) { data.add(x); } /** 判断是否为空 */ public boolean isEmpty() { return data.isEmpty(); } /** 获取栈顶元素 */ public int top() { return data.get(data.size() - 1); } /** 出栈 */ public boolean pop() { if (isEmpty()) { return false; } data.remove(data.size() - 1); return true; } }; public class Main { public static void main(String[] args) { MyStack s = new MyStack(); s.push(1); s.push(2); s.push(3); for (int i = 0; i < 4; ++i) { if (!s.isEmpty()) { System.out.println(s.top()); } System.out.println(s.pop()); } } }
Java内置的栈库
public class Test {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
//入栈
stack.push(2);
stack.push(4);
stack.push(1);
//出栈
Integer pop = stack.pop();
System.out.println(pop); //输出为1
//获取栈顶元素但不删除
Integer peek = stack.peek();
System.out.println(peek); //输出为4
//判断是否为空
System.out.println(stack.empty());
//栈的大小
System.out.println(stack.size());
}
}
也可用双端队列来实现栈的操作
public class Test {
public static void main(String[] args) {
Deque<Integer> stack = new LinkedList<>();
//入栈
stack.push(1);
stack.push(2);
//查看栈顶元素
System.out.println(stack.peek());
//出栈
Integer pop = stack.pop();
System.out.println(pop);
//判断是否为空
System.out.println(stack.isEmpty());
//栈的大小
System.out.println(stack.size());
}
}
155.最小栈
-
题目描述:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x)
—— 将元素 x 推入栈中。
pop()
—— 删除栈顶的元素。
top()
—— 获取栈顶元素。
getMin()
—— 检索栈中的最小元素。 -
分析
以空间换时间,我们可以再额外增加一个辅助栈来存放原栈中所有元素对应的最小值,即在每个元素入栈时把当前栈的最小值 存储起来。这样,辅助栈的栈顶元素即为最小元素。
-
代码
class MinStack { Stack<Integer> stack; //存放对应最小值的栈 Stack<Integer> minStack; /** initialize your data structure here. */ public MinStack() { stack = new Stack<>(); minStack = new Stack<>(); } public void push(int val) { if(minStack.empty() || val < minStack.peek()) { minStack.push(val); }else{ minStack.push(minStack.peek()); } stack.push(val); } public void pop() { stack.pop(); minStack.pop(); } public int top() { return stack.peek(); } public int getMin() { return minStack.peek(); } }
20.有效的括号
-
题目描述:给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效。给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:左括号必须用相同类型的右括号闭合;左括号必须以正确的顺序闭合。
-
示例
输入:s = "()" 输出:true 输入:s = "(]" 输出:false
-
分析
括号匹配问题可用栈来实现。
我们遍历字符串
s
,如果当前字符是左括号,就入栈;如果当前字符是右括号,栈顶元素出栈,并判断是否与右括号匹配。这样我们保证了栈中只存放了左括号,并且能正确的与相应右括号匹配。
如果最后栈中还有元素,说明有多余的左括号,返回false。
-
代码
class Solution { public boolean isValid(String s) { Map<Character,Character> map = new HashMap<>(); map.put(')','('); map.put(']','['); map.put('}','{'); Stack<Character> stack = new Stack<>(); //遇到左括号入栈,遇到右括号出栈 for(int i=0; i<s.length(); i++) { char cur = s.charAt(i); //当前字符为右括号,出栈 if(map.containsKey(cur)) { if(stack.empty() || stack.pop()!=map.get(cur)) { return false; } }else { //为左括号,入栈 stack.push(cur); } } return stack.empty(); } }
739.每日温度
-
题目描述:请根据每日
气温
列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用0
来代替。例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:
气温
列表长度的范围是[1, 30000]
。每个气温的值的均为华氏度,都是在[30, 100]
范围内的整数。 -
分析
此题可直接暴力求解,也就是找到当前元素之后第一个大于当前气温的元素,时间复杂度为
O(n*n)
。更巧妙的方法是使用单调栈,我们用一个栈来存放数组的下标,其中下标对应的气温从栈底到栈顶递减。
具体做法就是遍历数组,如果栈为空,就直接将当前下标加入栈;如果当前气温大于栈顶元素对应的气温,那么说明栈顶下标对应的气温要升高至少等待的天数就为当前气温下标与栈顶下标之差,将栈顶元素出栈,一直循环直至当前气温不大于栈顶元素对应的气温。
然后将当前下标入栈,这样我们又再次维护了单调栈。
-
代码
class Solution { public int[] dailyTemperatures(int[] temperatures) { int[] res = new int[temperatures.length]; //单调栈,用于记录下标,从栈底到栈顶下标对应的气温递减 Stack<Integer> stack = new Stack<>(); for(int i=0; i<temperatures.length; i++) { //当前温度大于栈顶元素时 while(!stack.empty() && temperatures[i] > temperatures[stack.peek()]) { int j = stack.pop(); res[j] = i - j; } stack.push(i); } return res; } }
150.逆波兰表达式求值
-
题目描述:根据 逆波兰表示法,求表达式的值。
有效的算符包括
+
、-
、*
、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。说明:
- 整数除法只保留整数部分。
- 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
-
示例
输入:tokens = ["2","1","+","3","*"] 输出:9 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
-
分析
栈的典型应用。遍历数组,遇到数字入栈,遇到运算符将两个元素出栈,并将计算结果入栈。最终栈中的值即为结果。
-
代码
class Solution { public int evalRPN(String[] tokens) { int n = tokens.length; //用栈存放数组中的数字 Stack<Integer> stack = new Stack<>(); Set<String> set = new HashSet<>(); set.add("+"); set.add("-"); set.add("*"); set.add("/"); for(int i=0; i<n; i++) { if(set.contains(tokens[i])) { //遇到运算符时就从栈中取出两个数字计算 int a = stack.pop(); int b = stack.pop(); if(tokens[i].equals("+")) { stack.push(b+a); } else if(tokens[i].equals("-")) { stack.push(b-a); } else if(tokens[i].equals("*")) { stack.push(b*a); } else if(tokens[i].equals("/")) { stack.push(b/a); } } else{ stack.push(Integer.valueOf(tokens[i])); } } return stack.peek(); } }