目录
栈与队列
栈:其实都是相邻消除的操作——左括号右括号匹配,相同元素消除,逆波兰表达式
队列:滑动窗口最大值,前k个高频元素(实现了对部分数据进行排序 O(nlogk) )
2024.3.18
232 用栈实现队列
Stack<Integer> stackIn;
Stack<Integer> stackOut
在leetcode中,有一个MyQueue类,来实现创建对象,进队列,出队列,获取第一个元素,判断是否为空的函数(MyQueue(),push(),pop(),peek(),empty())
先定义两个栈; 在MyQueue中初始化;pop()中要注意当出栈是空的时候才将进栈中的所有元素推出进到出栈中;peek()中可以复用类里面的函数,int result=this.pop(); 但是要注意pop将第一个元素移出了,要在将第一个元素推入栈中;empty()中,两个栈都为空时,才是队列为空。
2024.3.19
225 用队列实现栈
Queue<Integer> queue1
Queue<Integer> queue2
1.用两个队列实现:LinkedList<>()模拟列表,offer():用于往队列中添加一个元素,成功return,满false; poll():用于从队列中删除并返回第一个元素,空返回null。这两个方法都不会抛出异常。peek():获取第一个元素
最重要的就是push这里,进入之后用辅助队列将顺序弄成和栈一样的出的顺序,因为栈是后进先出,所以后进的那个元素先给queue2,再把queue1中的一个一个弄到queue2里面,就相当于后进的跑到了最前面,会第一个出来,最先进去的永远在最后;之后将queue2和queue1互换,始终让queue1与栈的顺序一致;接下来的操作中直接调用命令就可
public void push(int x) {
queue2.offer(x); // 先放在辅助队列中
while(!queue1.isEmpty()){
queue2.offer(queue1.poll());
}
Queue<Integer> queueTemp;
queueTemp = queue1;
queue1 = queue2;
queue2 = queueTemp; // 最后交换queue1和queue2,将元素都放到queue1中
}
2. 用一个队列实现:这时候就要用到Deque双端队列
Deque 接口继承了 Queue 接口,所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
Deque<Integer> que1;
que1=new ArrayDeque<>();
Deque有两个头,从最后加入为addLast,在出的时候,为了让最后一个先出,将前面size-1长度的first元素全都加入到最后,那么first就是要出的,用pollFirst;获得栈顶top元素的时候要注意是在deque的Last位置,最后一个进的,用peekLast。
public int pop() {
int size=que1.size();
size--;
while(size-->0){
que1.addLast(que1.pollFirst());
}
int res=que1.pollFirst();
return res;
}
2024.3.20
20 有效的括号
首先元素个数一定是偶数,如果%2!=0,那么false;运用栈,如果遍历到左括号,就把对应的右输入栈中,如果遍历到右边的,就把对应的输出,这样就实现了括号对应,因为最后一个左括号肯定要对应第一个出栈的右括号。一共有三种情况:最后栈中剩于一个——多左括号;遍历到右括号的时候不匹配;还没遍历完,栈空了。
Deque<Character> deque = new LinkedList<>();
1047 删除字符串中的所有相邻重复项
遍历元素,如果栈为空,将元素放入栈中,然后遍历下一个与栈顶元素比较看是否相等,相等就将栈顶元素pop,不相等push。最后将栈转换为字符串的时候出栈顺序是反的,可以定义一个空的字符串,每次
String str="";
while(!deque.isEmpty()){
str=deque.pop()+str;
}
可以直接使用字符串来模拟栈,用top代表新字符串的下标,初始为空值为-1,当top>=0并且top所对应的元素与遍历的元素相等,使用stack.deleteCharAt(top)将这个元素删除,并且top--;否则将遍历的元素加入到字符串中,stack.append(ch),top++; 输出的时候直接stack.toString()即可。
StringBuffer stack = new StringBuffer();
2024.3.21
※150 逆波兰表达式求值
后缀表达式,相当于二叉树的后序遍历:左右中(可以直接按遍历的顺序计算不会出错)
前序遍历:根左右;中序:左根右(要想正确计算表达式,还要加上括号)
遍历字符串,遇到数字进栈,遇到运算符,取出两个元素进行计算,并将得数加入栈中继续遍历;先遍历到num1,num1进,然后到num2,num2进,下一个如果是运算符,那么先pop出的是num2,后出num1,因此应该num1(-+*/)num2;也就是pop出的元素顺序与之前运算顺序相反
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> q=new LinkedList<>();
for(String i:tokens){
if("+".equals(i)||"-".equals(i)||"*".equals(i)||"/".equals(i)){
int num1=q.peek();
q.pop();
int num2=q.peek();
q.pop();
if("+".equals(i)){
q.push(num2+num1);
}
else if("-".equals(i)){
q.push(num2-num1);
}
else if("*".equals(i)){
q.push(num2*num1);
}
else if("/".equals(i)){
q.push(num2/num1);
}
}
else{
q.push(Integer.valueOf(i));
}
}
return q.pop();
}
}
leetcode内置jdk的问题,不能使用==判断字符串是否相等。最后出栈是String类型,转换为整型为Integer.valueOf(i)
2024.3.25
※239 滑动窗口最大值
很像一个队列,有出有进,但是要找最大值就不好弄了——单调队列(保证头部一直是此时滑动窗口中的最大值)
ArrayDeque<Integer> deque =new ArrayDeque<>();
int n=nums.length;
int[] res=new int[n-k+1]; //存放结果的数组,存放下标
int idx=0; //数组下标
for(int i=0;i<n;i++){
//i为nums下标,要在从[i-k+1,i]窗口中找最大值,刚开始没有到达第一个窗口的长度还不移动
//1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peekFirst() < i - k + 1){
deque.poll(); //移除头部元素
}
//每次滑动窗口,即遍历到一个新的元素时,看队列中末尾元素与这个元素的大小,
//如果比这个元素小,就将末尾poll,直到队列里面没有比新加的元素小的数;这样就保证了头部的下标所指的元素最大
while(!deque.isEmpty() && nums[i]>nums[deque.peekLast()]){
deque.pollLast();
}
deque.offer(i); //在队列尾部插入元素
// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果
if(i>=k-1){
res[idx++]=nums[deque.peekFirst()];
}
}
return res;
也可以将移除元素(第一个while)换成下面的代码:上面是根据下标是否在滑动窗口内来删除元素,下面是看窗口每次移出的是不是最大值,如果是的话那在这次循环中滑动窗口中已经没有这个值了,队列中也要移除。当i=k-1的时候,是第一个窗口,这是就应该存下来最大值。对于每次循环i指的是滑动窗口的最后一位(新加入的),区间为[i-k+1,i],前一位为i-k,当i-k>=0时,就要开始判断是否移出了。
//当窗口开始移动,即i>=k,这时候i-k就是当前滑动窗口相较于前一个移出的元素,
//就判断这个元素和队列中的最大值即头部是否相等,相等的话也要从队列中移出
if(!deque.isEmpty() && i>=k && nums[i-k]==nums[deque.peekFirst()]){
deque.poll(); //移除头部元素
}
感觉这个更容易理解,先判断是否移除,再加入,最后保存每次的最大值。
ArrayDeque<Integer> deque =new ArrayDeque<>();
int n=nums.length;
int[] res=new int[n-k+1];
int idx=0; //数组下标
for(int i=0;i<n;i++){
if(!deque.isEmpty() && i>=k && nums[i-k]==nums[deque.peekFirst()]){
deque.poll();
}
while(!deque.isEmpty() && nums[i]>nums[deque.peekLast()]){
deque.pollLast();
}
deque.offer(i);
if(i>=k-1){
res[idx++]=nums[deque.peekFirst()];
}
}
return res;
2024.3.27
※347 前k个高频元素
大顶堆(根节点元素最大),小顶堆(根节点元素最小)使用小顶堆每次才能把次数最小的元素从根节点poll出去;
先使用HashMap存储元素以及出现的次数,key:元素,value:次数;
Map<Integer,Integer> occur = new HashMap<Integer,Integer>();
for(int num:nums){
occur.put(num, occur.getOrDefault(num, 0) + 1);
}
※初始化小顶堆:使用 PriorityQueue
,并定义了一个比较器,使其按照 int[]
的第二个元素(即出现次数)进行排序。由于这是一个默认的最小堆,堆顶元素总是出现次数最小的元素。
- 计算两个数组第二个元素(出现次数)的差值。
- 如果
m[1]
大于n[1]
,则差值为正,表示m
应该在n
之前(在最小堆中,较小的值在顶部,所以出现次数较多的应该在后面)。 - 如果
m[1]
小于n[1]
,则差值为负,表示m
应该在n
之后。 - 如果
m[1]
等于n[1]
,则差值为0,表示m
和n
的顺序无关紧要
// int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
遍历HashMap并更新小顶堆;
Map.Entry<Integer, Integer> entry
定义了一个Map.Entry
类型的变量entry,
occur.entrySet()
:这个调用返回occur
映射的Set
视图,它包含occur
中的所有键值对。
for (Map.Entry<Integer, Integer> entry : occur.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < count) {
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
queue.offer(new int[]{num, count});
}
}
int[] ret = new int[k];
for (int i = 0; i < k; ++i) {
ret[i] = queue.poll()[0];
}
return ret;
queue.peek()[1]表示堆顶元素出现的次数,因为int[]中第二个元素代表次数;queue.poll()[0]就是输出这个元素。