今天是lc刷题第三篇
主题:栈,队列 方式:题目记录
栈和队列的相互实现
- 使用栈去实现队列
思路:
使用两个栈,入了第一个栈之后pop出来入第二个栈
再执行pop操作即可
- 使用队列去实现栈
可以使用两个队列,但是比较简单,在这儿就不说了
这里说下使用一个队列去实现栈:首先先将所有元素入队,之后将队列中的(size - 1) 依次推出重新入队,之后再进行出队操作即可
有效的括号
需求分析:对字符串进行匹配
特征:前后匹配 ---> 解法:使用栈
在做本题之前补充一个小知识:


思考流程:对于这类题,优先考虑返回false的情况,其余均为true
class Solution {
public boolean isValid(String s) {
// 需求:对字符串进行匹配 特征 前后匹配 ---> 栈
// 考虑不匹配情况,其余均返回true
if (s.length() % 2 != 0){
return false;
}
Deque<Character> stack = new LinkedList<>();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (ch == '(') {
stack.push(')');
}else if (ch == '{') {
stack.push('}');
}else if (ch == '[') {
stack.push(']');
}else if(stack.isEmpty() || ch != stack.peek()){
return false;
} else{
stack.pop();
}
}
boolean isEmpty = stack.isEmpty();
return isEmpty;
}
}
也可以考虑使用双指针的方式去模仿栈,提高效率
class Solution {
public String removeDuplicates(String s) {
// 需求:消除相邻相同元素
// 特征:相邻匹配问题
// 如何遍历一个栈
char[] chars = s.toCharArray();
int slow = 0,fast = 0;
while(fast < chars.length){
chars[slow] = chars[fast];
if(slow > 0 && chars[slow] == chars[slow - 1]){
// 进行下一轮时 slow-- 位置的字符会被覆盖
slow--;
}else {
slow++;
}
fast++;
}
return new String(chars,0,slow);
}
}
逆波兰表达式求值
后缀表达式切换为中序遍历方式的表达式
其中需要进行添加括号操作,数字前后均需要添加括号,但是我们也不能提起放置好括号在栈,因为我们不知都后续的操作符会是什么
那么此时,考虑到的方法就是,将数字取出,进行额外处理,但是这里还要进行下一步处理,就是将这几个字符绑定成一个新的字符重新放入栈中,方便后续操作
但是本题只需要一个结果那么直接拉出来操作返回数字即可,不需要考虑优先级,但是别忘了-和除是需要进行特殊处理的
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList();
for (String s : tokens) {
if ("+".equals(s)) {
stack.push(stack.pop() + stack.pop());
} else if ("-".equals(s)) {
// 注意 - 和/ 需要特殊处理
stack.push(-stack.pop() + stack.pop());
} else if ("*".equals(s)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(s)) {
int temp1 = stack.pop();
int temp2 = stack.pop();
stack.push(temp2 / temp1);
} else {
stack.push(Integer.valueOf(s));
}
}
return stack.pop();
}
}
滑动窗口最大值
特征:一个不断变化的局部 且需要维护其中的数据 ————> 单调队列
根据需求,需要考虑的就是单调队列中每次弹出的数据需要是最大的 但是还需要保证在动态变化,每次需要动态的进行元素的变动 即元素的进出
那么,如何对队列安排进出条件才可以呢?
我们回到需求中,需求每次在局部中获取到最大值并返回
队列中获取元素最简单的方式就是获取第一个,需要进行排序,但是排序之后就无法满足弹出元素这个条件
那么此时我们可以想到也许不需要维护所有的元素,只留下局部中较大的值即可
那么此时我们自己创建这样一个数据结构
里面有三个方法
-
push函数 对比传入元素val 若大,就将队列中所有比他小的推出 若小,就留下
-
pop函数 对比当前传入元素是否为被移动出窗口的元素,若是则推出 若不是则继续
-
peek函数 获取队头元素
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
//
void pop(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
void push(int val) {
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();
}
deque.add(val);
}
int peek() {
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 需求:每次获取一个局部,取得最大值
// 特征:一个不断变化的局部 且需要维护其中的数据
MyQueue queue = new MyQueue();
if (nums.length == 1) {
return nums;
}
int len = nums.length - k + 1;
int[] res = new int[len];
int num = 0;
for (int i = 0; i < k; i++) {
queue.push(nums[i]);
}
res[num++] = queue.peek();
for (int i = k; i < nums.length; i++) {
queue.pop(nums[i - k]);
queue.push(nums[i]);
res[num++] = queue.peek();
}
return res;
}
}
前K个高频元素
需求:
-
对每个元素进行频次统计
-
进行排序
-
获取排序之后的结果
主要需求为前面二者
特征:大数值获取高频低频
对应解法:使用优先级队列(底层为堆)(数据结构特点是有序)+ map存储
本题采用大根堆小根堆的方式均可
-
大根堆时间复杂度为O(nlogn)
-
小根堆时间复杂度为O(nlogk)
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);
}
// 这里使用lambda表达式的方式去写比较器
// 将map传入 转换为数组,对比的是每一个value 这里是小根堆方式 (若为pair2 - pair1)则为大根堆
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(pq.size()<k){//小顶堆元素个数小于k个时直接加
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
if(entry.getValue()>pq.peek()[1]){
pq.poll();
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] res = new int[k];
for(int i=k-1;i>=0;i--){//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
res[i] = pq.poll()[0];
}
return res;
}
}