算法之路从0到1 Day 11栈与队列part 02

Day 11栈与队列part 02

150. 逆波兰表达式求值

想到的问题:如何判断这个字符是符号还是数字

可以直接用equals判断.

==号:

  1. 如果是基本数据类型比较的是值

  2. 引用数据类型比较的是地址值是否相等

equals:

  1. 比较的是地址值

  2. 提供方法重写自定义,可以比较引用数据类型的内容

class Person {
    String name;
    int age;
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true; // 如果是同一个对象
        if (obj == null || getClass() != obj.getClass()) return false; // 如果对象为空或类型不同
        Person person = (Person) obj; // 类型转换
        return age == person.age && Objects.equals(name, person.name); // 比较内容
    }
}

题目给出的数据都是合法的

存字符串

private static int evalRPN(String[] tokens) {
        Stack<String> stack = new Stack<>();
        for (String token : tokens) {
            if (token.equals("*")) {
                Integer b = Integer.valueOf(stack.pop());
                Integer a = Integer.valueOf(stack.pop());
                String result = String.valueOf(a * b);
                stack.push(result);
            } else if (token.equals("/")) {
                Integer b = Integer.valueOf(stack.pop());
                Integer a = Integer.valueOf(stack.pop());
                String result = String.valueOf(a / b);
                stack.push(result);
            } else if (token.equals("+")) {
                Integer b = Integer.valueOf(stack.pop());
                Integer a = Integer.valueOf(stack.pop());
                String result = String.valueOf(a + b);
                stack.push(result);
            } else if (token.equals("-")) {
                Integer b = Integer.valueOf(stack.pop());
                Integer a = Integer.valueOf(stack.pop());
                String result = String.valueOf(a - b);
                stack.push(result);
            } else {
                stack.push(token);
            }
        }
        return Integer.parseInt(stack.pop());//valueOf
    }

存数值

除了除号以外都是能够,直接操作

private static int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (String token : tokens) {
            if (token.equals("*")) {
//                Integer b = stack.pop();
//                Integer a = stack.pop();
//                stack.push(a * b);
                stack.push(stack.pop() * stack.pop());
            } else if (token.equals("/")) {
                Integer b = stack.pop();
                Integer a = stack.pop();
                stack.push(a / b);
            } else if (token.equals("+")) {
//                Integer b = stack.pop();
//                Integer a = stack.pop();
//                stack.push(a + b);
                stack.push(stack.pop() + stack.pop());
            } else if (token.equals("-")) {
//                Integer b = stack.pop();
//                Integer a = stack.pop();
//                stack.push(a - b);
                stack.push(-stack.pop() + stack.pop());
            } else {
                stack.push(Integer.valueOf(token));
            }
        }
        return stack.pop();
    }

parseInt和valueOf的区别

parseInt:

  1. 不会自动装箱为Integer,直接解析为int

valueOf:

  1. 会先变为Integer,如果要返回的是int,则会再次拆箱。

两者 不合法会报异常

239. 滑动窗口最大值

用双端队列来自定义单调队列 思路 我们实现自定义方法

  1. 遍历到元素,若此元素等于 队列的出口,就弹出出口处,
  2. 若此元素,队列不为空,大于队列入口的元素,就while,将小的元素,从入口处弹出
  3. 返回的是队列出口的元素,维护队列出口的元素,为最大值即可

注意开头判断,nums的长度,确定result数组的长度

一开始我们先装入k个,拿一次最大值。之后再for循环,自动操作

弹出的时候,只判断一次。根据nums[num-k]和队首

//!deque.isEmpty()非空判断是必须的,加入的值,比队尾的元素大就要弹出

deque和queue,前者为双端队列,两端都可以插入和弹出

错误点:

加入元素,val与队尾元素的判断
条件类型行为适用场景风险
val > (正确)保留所有可能成为最大值的重复元素通用场景,保证窗口完整性
val >= (错误)提前移除重复的候选最大值窗口滑动时可能丢失最大值,导致错误结果
获取最大元素,是否弹出的问题
操作目的关键影响
myQueue.peek()安全获取当前窗口最大值不破坏队列结构
myQueue.poll(int val)清理过期元素仅在必要时移除队首

代码(含详细注释)

public class Sliding_Window_Maximum {
    static class MyQueue {   //类不是方法,不加括号
        Deque<Integer> deque = new LinkedList<>();

        //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
        //同时判断队列当前是否为空
        void poll(int val) {
            //是if不是while
            if (!deque.isEmpty() && val == deque.peek()) {
                deque.poll();//deque.pollFirst();
            }
        }

        //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
        //保证队列元素单调递减
        //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
        void add(int val) {
            //注意是前k个元素
            //!deque.isEmpty()非空判断是必须的,加入的值,比队尾的元素大就要弹出
            //大于还是大于等于,区别在何处,加上个等于会出错
            //保留所有可能成为最大值的重复元素 ||val >= (错误)    提前移除重复的候选最大值
            while (!deque.isEmpty() && val > deque.getLast()) {
                deque.removeLast();
            }
            deque.add(val);
        }

        //队列队顶元素始终为最大值
        //记录先不弹出,若弹出会出问题,因为我们循环开始要做判断,弹出会影响之后循环的判断
        //获取最大值不改变队列结构,有的这个最大值,后面几次同样也是最大值,提前弹出影响后面
        int peek() {
            return deque.peek();//deque.peekFirst()
        }
    }

    private int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 1) {
            return nums;//只有一个元素,最大值就是它本身
        }
        //k的取值,最大不超过 nums.length   2-2+1 k最大值保底
        int len = nums.length - k + 1;//最大值数len至少要为一,只要nums>=1
        //存放结果元素的数组
        int[] res = new int[len];
        int num = 0;//当作结果下标用
        //自定义队列
        MyQueue myQueue = new MyQueue();
        //起始操作
        //先将前k的元素放入队列
        for (int i = 0; i < k; i++) {
            myQueue.add(nums[i]);  // 本人错误一开始把num用在此处
        }
        res[num++] = myQueue.peek();//本k个元素最大数
        for (int i = k; i < nums.length; i++) {
            //滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
            //有进有出
            myQueue.poll(nums[i - k]);
            //滑动窗口加入最后面的元素   窗口右移  
            myQueue.add(nums[i]);
            //记录对应的最大值
            res[num++] = myQueue.peek();
        }
        return res;
    }
}

347. 前 K 个高频元素

维护k个元素,如果采用大顶堆,当我们插入一个元素,就会弹出一个,这样只能维护前k小的元素,而不是最大的。

我们PriorityQueue 存入的是pair类,存着的是元素和出现的频率,我们排序是排序频率,所以我们这里采用的是PriorityQueue 里面的自定义排序

如果有限级队列的大小大于k,我们就弹出(插入的时候我们就已经排过序了,小的在最顶,弹出的为小的)

我们这里可以使用数组,结构会简单一下

大根堆和小根堆的实现区别在于自定义排序规则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

pair2[1] - pair1[1] ,pair2[1]比pair1[1] 大,就pair2[1]排前面 ,降序 大->小

PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);//大
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);//小

注意:这里的pair名字是自己给的,出门在外,名号都是自己给的

(pair1[1], pair2[1])->pair2[1] - pair1[1])这个是错误的   我们只用写数组名称就可以

传入int[]数组作为对象, 插入 居然可以用new int[]{} 直接在花括号中填入构造数组的数据,我们取出元素的时候,也可以用[] 来指定我们要取出的位置

PriorityQueue<int[]> pairs = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);

pairs.add(new int[]{it.getKey(), it.getValue()});

 ans[i] = pairs.poll()[0];

基于小顶堆实现

  1. 我们先用map对数据及频率进行排序。

  2. 有限级队列 存放的是对象,自定义排序,按照对象(数组)的频率进行排序

  3. 未满k个就插入||满了就对比频率(堆顶,频率是优先级队列中最小的),大于堆顶就堆顶弹出,数据插入,

  4. 我们最后需要返回,频率从大到小填到数组中,有技巧得用for循环

    //基于小顶堆实现
    private static int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();//多态
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        //自定义排序
        //在优先队列中存储二元组(num, cnt),cnt表示元素值num在数组中的出现次数
        PriorityQueue<int[]> pairs = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
        for (Map.Entry<Integer, Integer> it : map.entrySet()) {
            //没满,就填充
            if (pairs.size() < k) {//小顶堆元素个数小于k个时直接加
                pairs.add(new int[]{it.getKey(), it.getValue()});
            } else {  //满了就选择性加入
                if (it.getValue() > pairs.peek()[1]) {//注意这里是频率排序,也就比较第二位的value
                    pairs.poll(); //弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
                    pairs.add(new int[]{it.getKey(), it.getValue()});
                }
            }
        }
        int[] ans = new int[k];
        //要保持频率从大到小收集
        //依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
        for (int i = k - 1; i >= 0; i--) {
            ans[i] = pairs.poll()[0];
        }
        return ans;
    }

基于大顶堆实现

不管填入多少,我们在最后for循环只取前k个

  //基于大顶堆实现
    private static int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();//多态
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        //自定义排序
        PriorityQueue<int[]> pairs = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);
        for (Map.Entry<Integer, Integer> it : map.entrySet())a {
            //不管填入多少,我们在最后只取前k个
            pairs.add(new int[]{it.getKey(), it.getValue()});
        }
        int[] ans = new int[k];
        for (int i = 0; i < k; i++) {
            ans[i] = pairs.poll()[0];
        }
        return ans;
    }

定义泛型Pair类

1. 基础结构设计
public class Pair<L, R> {
    private final L left;   // 左值(不可变)
    private final R right;  // 右值(不可变)

    public Pair(L left, R right) {
        this.left = left;
        this.right = right;
    }

    public L getLeft() { return left; }
    public R getRight() { return right; }

    @Override
    public String toString() {
        return "Pair(" + left + ", " + right + ")";
    }
}

关键点

:使用泛型<L, R>

支持任意类型组合,字段设为final确保不可变性

扩展

:若需可变性,可添加setLeft()和setRight()

方法(参考MutablePair实现)


二、堆(PriorityQueue)中的使用

2.1 自定义比较逻辑

Pair需要按某个字段排序时,需提供比较器(Comparator):

// 示例:按Pair的右值升序排列(小顶堆)
PriorityQueue<Pair<Integer, String>> minHeap = new PriorityQueue<>(
    Comparator.comparingInt(pair -> pair.getRight())
);

// 或按左值降序排列(大顶堆)
PriorityQueue<Pair<String, Double>> maxHeap = new PriorityQueue<>(
    (p1, p2) -> p2.getLeft().compareTo(p1.getLeft())
);

说明

:使用 Comparator.comparingInt()或Lambda表达式定义排序规则
2.2 完整堆操作示例

    public static void main(String[] args) {
        // 创建按右值(整数)排序的小顶堆
        PriorityQueue<Pair<String, Integer>> pq = new PriorityQueue<>(
            Comparator.comparingInt(Pair::getRight)
        );

        // 添加元素
        pq.add(new Pair<>("TaskA", 3));
        pq.add(new Pair<>("TaskB", 1));
        pq.add(new Pair<>("TaskC", 2));

        // 弹出元素(按优先级升序)
        while (!pq.isEmpty()) {
            Pair<String, Integer> task = pq.poll();
            System.out.println(task.getLeft() + " (优先级:" + task.getRight() + ")");
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值