代码随想录第十一天|150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素

题目:150. 逆波兰表达式求值

根据逆波兰表示法,求表达式的值。

有效的运算符包括 +-*/。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的,即表达式总能得出有效数值且不存在除数为 0 的情况。

解题思路

逆波兰表达式的特点是操作数在前,运算符在后,计算时从左到右扫描,并在遇到运算符时进行计算。因此可以使用栈来辅助计算:

  1. 使用栈存储数字
    • 遍历表达式中的每个元素,遇到数字则入栈。
    • 遇到运算符时,弹出栈顶的两个数字进行计算,将结果入栈。
  2. 逆序操作
    • 因为 num2 - num1num2 / num1 的顺序要求,我们需注意先弹出的数字为右操作数,第二个弹出的是左操作数。
  3. 最终结果
    • 扫描完表达式后,栈中只剩一个元素,即为计算结果。

反思

  1. 栈的使用:栈的后进先出特性很适合逆波兰表达式的求值。每次遇到运算符时取出两个操作数,可以实现正确的运算顺序。
  2. 操作符的判断:在Java中需要使用 .equals() 来判断字符串是否相等。需要特别注意 "/""-" 的运算顺序。
  3. 边界处理:因为题目保证表达式有效,因此不用处理异常情况,如除零、非法字符等。

代码

class Solution {
    public int evalRPN(String[] tokens) {
		Deque<Integer> nums = new ArrayDeque<>();
        for(int i=0;i<tokens.length;i++){
			if (tokens[i].equals("+")) {
				int num1=nums.pop();
				int num2=nums.pop();
				nums.push(num1+num2);
			} else if (tokens[i].equals("-")) {
				int num1=nums.pop();
				int num2=nums.pop();
				nums.push(num2-num1);
			} else if (tokens[i].equals("*")) {
				int num1=nums.pop();
				int num2=nums.pop();
				nums.push(num2*num1);
			}else if (tokens[i].equals("/")) {
				int num1=nums.pop();
				int num2=nums.pop();
				nums.push(num2/num1);
			}else {
				nums.push(Integer.parseInt(tokens[i]));
			}

		}
		return nums.pop();
    }
}

题目:239. 滑动窗口最大值

给定一个整数数组 nums 和一个大小为 k 的滑动窗口,该窗口从数组的最左侧移动到最右侧。每次只能看到滑动窗口内的 k 个数字,并且滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。


解题思路

可以使用双端队列 deque 来实现滑动窗口最大值的计算。deque 维护当前滑动窗口的最大元素的索引,保证队列的第一个元素为当前窗口的最大值的索引:

  1. 维护双端队列:对于每个新元素,检查队列的有效性,移除不在窗口范围内的元素(索引小于当前窗口的左边界)。
  2. 移除较小元素:对于即将加入窗口的元素,移除队列尾部比当前元素小的所有元素,确保队列从前到后保持一个递减的顺序。
  3. 窗口最大值:每次当索引超过窗口大小时,将队列头部的元素索引对应的值作为窗口的最大值记录到结果数组中。

反思

  1. 双端队列的优势:利用双端队列,可以在 O(n) 的时间复杂度内完成滑动窗口最大值的求解。每个元素最多进出队列一次,效率极高。
  2. 边界检查:在 poll 方法中,用 if 语句代替 while,可以减少不必要的重复检查并提高代码可读性。
  3. 优化添加和移除操作:在每次移除和添加元素时,利用 deque 的头部和尾部操作保证队列内容和窗口范围的一致性。

代码

class MyQueue{
		Deque<Integer> deque = new LinkedList<>();
		void poll(int val){
			if(!deque.isEmpty() && val==deque.peek()){
				deque.poll();
			}
		}
		void add(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) {
		if(nums.length == 1){
			return nums;
		}
		int len = nums.length-k+1;
		int[] res = new int[len];
		MyQueue myQueue = new MyQueue();
		int num = 0;
		for(int i=0;i<k;i++){
			myQueue.add(nums[i]);
		}
		res[num++] = myQueue.peek();
		for(int i=k;i<nums.length;i++){
			myQueue.poll(nums[i-k]);
			myQueue.add(nums[i]);
			res[num++] = myQueue.peek();
		}
		return res;
    }
}
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
		ArrayDeque<Integer> deque = new ArrayDeque<>();
		int n = nums.length;
		int[] res = new int[n - k + 1];
		int idx=0;
		for(int i=0;i<nums.length;i++){
			while(!deque.isEmpty() && deque.peek() < i-k+1){
				deque.poll();
			}
			while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]){
				deque.pollLast();
			}
			deque.offer(i);
			if(i>=k-1){
				res[idx++] =  nums[deque.peek()];
			}
		}
		return res;
    }
}

347.前 K 个高频元素

题目:347. 前 K 个高频元素

给定一个非空的整数数组 nums,返回其中出现频率前 k 高的元素。


解题步骤

  1. 构建频率哈希表:遍历数组 nums,记录每个元素出现的频率,使用 HashMap 存储元素及其对应的频率。
  2. 维护大小为 k 的优先队列:利用优先队列(堆)保存频率最高的前 k 个元素。
    • 大顶堆:直接将所有元素放入堆中,然后弹出前 k 个元素。
    • 小顶堆:逐步添加元素至堆中,超过 k 个时删除堆顶最小元素。堆中最后剩下的 k 个元素即为频率前 k 高的元素。
  3. 结果输出:将优先队列中的元素依次出队,存入结果数组。

反思

  1. 优先队列(堆)应用:使用小顶堆在存储容量为 k 的情况下实现频率筛选,从而减少时间复杂度。
  2. 空间优化:在大数据量下,利用 PriorityQueue 实现频率最高的前 k 个元素筛选,有效避免对整个数据集排序。
  3. 边界条件:注意当 k 大于 nums 中不同元素个数时,可能出现特殊情况。

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);
		}

		PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2) -> pair2[1] - pair1[1]);
		for(Map.Entry<Integer,Integer> entry : map.entrySet()){
			pq.add(new int[]{entry.getKey(),entry.getValue()});
		}
		int[] ans = new int[k];
		for(int i =0;i<k;i++){
			ans[i] = pq.poll()[0];
		}
		return ans;
    }
}
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 优先级队列,为了避免复杂 api 操作,pq 存储数组
        // lambda 表达式设置优先级队列从大到小存储 o1 - o2 为从小到大,o2 - o1 反之
        PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[1] - o2[1]);
        int[] res = new int[k]; // 答案数组为 k 个元素
        Map<Integer, Integer> map = new HashMap<>(); // 记录元素出现次数
        for (int num : nums) map.put(num, map.getOrDefault(num, 0) + 1);
        for (var x : map.entrySet()) { // entrySet 获取 k-v Set 集合
            // 将 kv 转化成数组
            int[] tmp = new int[2];
            tmp[0] = x.getKey();
            tmp[1] = x.getValue();
            pq.offer(tmp);
            // 下面的代码是根据小根堆实现的,我只保留优先队列的最后的k个,只要超出了k我就将最小的弹出,剩余的k个就是答案
            if(pq.size() > k) {
                pq.poll();
            }
        }
        for (int i = 0; i < k; i++) {
            res[i] = pq.poll()[0]; // 获取优先队列里的元素
        }
        return res;
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值