题目:150. 逆波兰表达式求值
根据逆波兰表示法,求表达式的值。
有效的运算符包括 +
、-
、*
、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
- 整数除法只保留整数部分。
- 给定逆波兰表达式总是有效的,即表达式总能得出有效数值且不存在除数为
0
的情况。
解题思路
逆波兰表达式的特点是操作数在前,运算符在后,计算时从左到右扫描,并在遇到运算符时进行计算。因此可以使用栈来辅助计算:
- 使用栈存储数字:
- 遍历表达式中的每个元素,遇到数字则入栈。
- 遇到运算符时,弹出栈顶的两个数字进行计算,将结果入栈。
- 逆序操作:
- 因为
num2 - num1
和num2 / num1
的顺序要求,我们需注意先弹出的数字为右操作数,第二个弹出的是左操作数。
- 因为
- 最终结果:
- 扫描完表达式后,栈中只剩一个元素,即为计算结果。
反思
- 栈的使用:栈的后进先出特性很适合逆波兰表达式的求值。每次遇到运算符时取出两个操作数,可以实现正确的运算顺序。
- 操作符的判断:在Java中需要使用
.equals()
来判断字符串是否相等。需要特别注意"/"
和"-"
的运算顺序。 - 边界处理:因为题目保证表达式有效,因此不用处理异常情况,如除零、非法字符等。
代码
java">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
维护当前滑动窗口的最大元素的索引,保证队列的第一个元素为当前窗口的最大值的索引:
- 维护双端队列:对于每个新元素,检查队列的有效性,移除不在窗口范围内的元素(索引小于当前窗口的左边界)。
- 移除较小元素:对于即将加入窗口的元素,移除队列尾部比当前元素小的所有元素,确保队列从前到后保持一个递减的顺序。
- 窗口最大值:每次当索引超过窗口大小时,将队列头部的元素索引对应的值作为窗口的最大值记录到结果数组中。
反思
- 双端队列的优势:利用双端队列,可以在
O(n)
的时间复杂度内完成滑动窗口最大值的求解。每个元素最多进出队列一次,效率极高。 - 边界检查:在
poll
方法中,用if
语句代替while
,可以减少不必要的重复检查并提高代码可读性。 - 优化添加和移除操作:在每次移除和添加元素时,利用
deque
的头部和尾部操作保证队列内容和窗口范围的一致性。
代码
java">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;}
}
java">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
高的元素。
解题步骤
- 构建频率哈希表:遍历数组
nums
,记录每个元素出现的频率,使用HashMap
存储元素及其对应的频率。 - 维护大小为 k 的优先队列:利用优先队列(堆)保存频率最高的前
k
个元素。- 大顶堆:直接将所有元素放入堆中,然后弹出前
k
个元素。 - 小顶堆:逐步添加元素至堆中,超过
k
个时删除堆顶最小元素。堆中最后剩下的k
个元素即为频率前k
高的元素。
- 大顶堆:直接将所有元素放入堆中,然后弹出前
- 结果输出:将优先队列中的元素依次出队,存入结果数组。
反思
- 优先队列(堆)应用:使用小顶堆在存储容量为
k
的情况下实现频率筛选,从而减少时间复杂度。 - 空间优化:在大数据量下,利用
PriorityQueue
实现频率最高的前k
个元素筛选,有效避免对整个数据集排序。 - 边界条件:注意当
k
大于nums
中不同元素个数时,可能出现特殊情况。
java">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;}
}
java">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;}
}