Leetcode 代码随想录——刷题笔记(Ⅱ)

目录

栈与队列

2024.3.18

232 用栈实现队列

2024.3.19

225 用队列实现栈

2024.3.20 

20 有效的括号

1047 删除字符串中的所有相邻重复项

 2024.3.21

※150 逆波兰表达式求值

2024.3.25

※239 滑动窗口最大值

2024.3.27

※347 前k个高频元素


栈与队列

栈:其实都是相邻消除的操作——左括号右括号匹配,相同元素消除,逆波兰表达式

队列:滑动窗口最大值,前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]就是输出这个元素。

  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值