java中栈和队列的实现方式:
先进后出
Deque<Integer> st = new LinkedList<>();
Deque是一个双端队列, 常用LinkedList 集合,它实现了Queue 接口;
LinkedList底层是一个双向链表
Deque堆栈操作方法:push()、pop()、peek()。
Deque队列操作方法:add()、offer()、remove()、poll()、peek()、
参考链接:http://t.csdnimg.cn/hrGK0
栈和队列:
用队列实现栈
用栈实现队列
232. 用栈实现队列
用两个栈实现队列,一个作为进栈,一个出栈,调整顺序。
class MyQueue {
Stack<Integer> stackIn = new Stack<Integer>();
Stack<Integer> stackOut = new Stack<Integer>();
public MyQueue() {
stackIn.clear();
stackOut.clear();
}
public void push(int x) {
stackIn.push(x);
}
public int pop() {
int i = this.peek();
return stackOut.pop();
}
public int peek() {
if(stackOut.empty()){
while(!stackIn.empty()){
stackOut.push(stackIn.pop());
}
}
return stackOut.peek();
}
public boolean empty() {
return stackOut.empty() && stackIn.empty();
}
}
225. 用队列实现栈
用两个队列实现栈,其中一个保存栈内的元素,另一个作为辅助队列,调证顺序,使新加入的元素永远位于第一个位置。
优化:一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。
class MyStack {
Queue<Integer> deque1 = new LinkedList<Integer>();
Queue<Integer> deque2 = new LinkedList<Integer>();
public MyStack() {
deque1.clear();
deque2.clear();
}
public void push(int x) {
deque2.add(x);
while(!deque1.isEmpty()){
deque2.add(deque1.remove());
}
while (!deque2.isEmpty()){
deque1.add(deque2.remove());
}
}
public int pop() {
return deque1.remove();
}
public int top() {
return deque1.peek();
}
public boolean empty() {
return deque1.isEmpty() && deque2.isEmpty();
}
}
匹配问题:
20.有效的括号
1047.删除字符串中的所有相邻重复项**(字符串去重)**
- 逆波兰表达式求值
涉及到之前遍历的元素,以及匹配问题可以考虑栈解决
20.有效的括号
- 左右括号数量相等,顺序相应
()[]{}
- 左右括号数量不相等
}或者{
- 左右括号数量相等,顺序不相应
(]
- 左右括号数量不相等,顺序不相应
分析:用栈解决匹配问题,当前元素如果为左括号栈中添加相应的右括号;如果为右括号,就与栈顶匹配(栈内需不为空),直到最后栈为空就满足条件。
注意Deque栈的增加和删除分别是push和pop
class Solution {
public boolean isValid(String s) {
if(s.length() % 2 == 1) return false;
Deque<Character> st = new LinkedList<>();
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);
if(ch == '(') st.push(')');
else if(ch == '{') st.push('}');
else if(ch == '[') st.push(']');
else if(!st.isEmpty() && ch == st.peek()){
st.pop();
}else{
return false;
}
}
if(st.isEmpty()) return true;
else return false;
}
}
1047.删除字符串中的所有相邻重复项
方法1: 栈(stack或者直接使用Stringbuffer作为栈)
class Solution {
public String removeDuplicates(String s) {
Stack<Character> st = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if(st.empty()){
st.push(c);
}else{
if(st.peek() == c){
st.pop();
}else{
st.push(c);
}
}
}
StringBuffer list = new StringBuffer();
while(!st.empty()){
list.insert(0, st.pop());
}
return new String(list);
}
}
方法3: 由于本题是删除字符,可以用快慢指针
class Solution {
public String removeDuplicates(String s) {
int slow = 0, fast = 0;
char[] chars = s.toCharArray();
while (fast < s.length()){
chars[slow] = chars[fast];
if(slow > 0 && chars[slow] == chars[slow-1]){
slow--;
}else{
slow++;
}
fast++;
}
return String.valueOf(chars, 0, slow);
}
}
- 逆波兰表达式求值
注意,减法和除法计算与弹栈的顺序问题
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> st = new Stack<>();
for (String t: tokens){
if(t.equals("+")){
int a = st.pop();
int b = st.pop();
st.push(a+b);
}else if(t.equals("-")){
int a = st.pop();
int b = st.pop();
st.push(b-a); //注意减数与被减数
}else if(t.equals("*")){
int a = st.pop();
int b = st.pop();
st.push(a*b);
}else if(t.equals("/")){
int a = st.pop();
int b = st.pop();
st.push(b/a); //注意除数与被除数
}else{
int i = Integer.parseInt(t);
st.push(i);
}
}
return st.pop();
}
}
单调队列
一、单调队列的概念:
单调队列,即单调递减或单调递增的队列。
为了可以同时弹出队首和队尾的元素,一般需要使用双端队列。满足这种单调性的双端队列称作「单调队列」。
二、单调队列的性质:
-
队列中的元素在原来的列表中的位置是由前往后的(随着循环顺序入队)。
-
队列中元素的大小是单调递增或递减的。
三、单调队列的特点:
从队尾入列,队首或队尾出列。
四、单调队列的作用
单调队列一般用于求区间内的最值问题。
五、例题
- 滑动窗口最大值
239. 滑动窗口最大值
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int [] res = new int[nums.length-k+1];
//que 保存的数组下标
LinkedList<Integer> que = new LinkedList<>();
for (int i = 0; i < nums.length; i++) {
//保证队列单调递减,队首即为当前窗口最大值
while(!que.isEmpty() && nums[i] > nums[que.peekLast()]){
que.removeLast();
}
que.add(i);
//窗口左边界left,右边界i
int left = i - k + 1;
//如果队首下标小于左边界,表示窗口已经划过最大值,必须被删除
if(que.peek() < left){
que.removeFirst();
}
//当窗口内元素大于等于k时,开始保存
if(i + 1 >= k){
res[left] = nums[que.peek()];
}
}
return res;
}
}
官网版:更好理解一点
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int [] res = new int[nums.length-k+1];
//que 保存的数组下标
LinkedList<Integer> que = new LinkedList<>();
//先添加第一个窗口
for (int i = 0; i < k; i++) {
//保证队列单调递减,队首即为当前窗口最大值
while(!que.isEmpty() && nums[que.peekLast()] < nums[i]){
que.removeLast();
}
que.add(i);
}
//第一个最大值
res[0] = nums[que.peek()];
//然后再移动
for (int i = k; i < nums.length; i++) {
//保证队列单调递减,队首即为当前窗口最大值
while(!que.isEmpty() && nums[que.peekLast()] < nums[i]){
que.removeLast();
}
que.add(i);
int left = i - k;
if(que.peek() <= left){
que.remove();
}
res[i-k+1] = nums[que.peek()];
}
return res;
}
}
优先队列
优先级队列的内部是用堆来维护,而且优先级队列内部元素是自动依照元素的权值排列。
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,内部以二叉树的形式。
java中用PriorityQueue集合类实现
前 K 个高频元素
数组中的第K个最大元素
使用最小堆统计前k个最大的元素,使用最大堆统计前k个最小的元素
347. 前 K 个高频元素
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//统计元素出现次数
HashMap<Integer, Integer> map = new HashMap<>();
for (int i: nums){
map.put(i, map.getOrDefault(i,0)+1);
}
//构建最小堆,保存元素的值
PriorityQueue<Integer> que = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return map.get(o1) - map.get(o2);
}
});
//当出现次数大于最小值时就弹出最小值,一直维持大小为k的堆,就是前最大k的值
for(Map.Entry<Integer, Integer> entry:map.entrySet()){
if(que.size() < k){
que.add(entry.getKey());
}else if(entry.getValue() > map.get(que.peek())){
que.remove();
que.add(entry.getKey());
}
}
//输出
int [] res = new int[k];
int i = 0;
while(!que.isEmpty()){
res[i++] = que.remove();
}
return res;
}
}
- 数组中的第K个最大元素
class Solution {
public int findKthLargest(int[] nums, int k) {
//构建最小堆
PriorityQueue<Integer> que = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
//当出现次数大于最小值时就弹出最小值,一直维持大小为k的堆,就是前最大k的值
for(int i: nums){
if(que.size() < k){
que.add(i);
}else if(que.peek() < i){
que.remove();
que.add(i);
}
}
return que.peek();
}
}