150. 逆波兰表达式求值
思路:后缀表达式求值。利用栈来进行,我这里写了字符串转int的函数。其实这里可以用Integer.valueOf(s)。(惭愧,基础还是太垃圾了。)
时间复杂度O(n)
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < tokens.length; i++) {
if ("+".equals(tokens[i])) {
int num2 = stack.pop();
int num1 = stack.pop();
stack.push(num1 + num2);
} else if ("-".equals(tokens[i])) {
int num2 = stack.pop();
int num1 = stack.pop();
stack.push(num1 - num2);
} else if ("*".equals(tokens[i])) {
int num2 = stack.pop();
int num1 = stack.pop();
stack.push(num1 * num2);
} else if ("/".equals(tokens[i])) {
int num2 = stack.pop();
int num1 = stack.pop();
stack.push(num1 / num2);
} else {
stack.push(getInt(tokens[i]));
}
}
return stack.pop();
}
// 字符串转int
public int getInt(String s) {
char[] ch = s.toCharArray();
int result = 0;
for (int i = 0; i < ch.length; i++) {
if (ch[0] == '-' && i != 0) {
result -= (ch[i] - '0') * Math.pow(10, ch.length - 1 - i);
} else if (ch[0] != '-'){
result += (ch[i] - '0') * Math.pow(10, ch.length - 1 - i);
}
}
return result;
}
}
随想录中的解法
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList();
for (String s : tokens) {
if ("+".equals(s)) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
stack.push(stack.pop() + stack.pop()); // 注意 - 和/ 需要特殊处理
} else if ("-".equals(s)) {
stack.push(-stack.pop() + stack.pop());
} else if ("*".equals(s)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(s)) {
int temp1 = stack.pop();
int temp2 = stack.pop();
stack.push(temp2 / temp1);
} else {
stack.push(Integer.valueOf(s));
}
}
return stack.pop();
}
}
239. 滑动窗口最大值
思路:自定义一个单调队列(可以返回队列中元素最大值的队列)。存入元素的时候,如果前面有比存入元素小的,先将其弹出,再push元素进去,这样保证队列中第一个元素永远是最大的元素。poll元素的时候,如果要poll的元素比队列中第一个元素小,说明该元素已经poll出去了,不需要做任何操作,如果等于队列中第一个元素,执行poll操作。获取队列中的最大值就是获取队列中的第一个元素。
时间复杂度O(n)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 自定义单调队列
Deque<Integer> que = new ArrayDeque<>();
int[] result = new int[nums.length + 1 - k];
int index = 0;
for (int i = 0; i < k; i++) {
push(que, nums[i]);
}
result[index++] = getMaxValue(que);
for (int i = k; i < nums.length; i++) {
pop(que, nums[i - k]);
push(que, nums[i]);
result[index++] = getMaxValue(que);
}
return result;
}
public void pop(Deque<Integer> que, int x) {
// 如果要pop的是单调队列中的第一个元素,即最大元素,进行pop,否则不做操作。
// 因为比第一个元素小的已经pop出去了
if (!que.isEmpty() && que.peekFirst() == x) {
que.pollFirst();
}
}
public void push(Deque<Integer> que, int x) {
while (!que.isEmpty() && que.peekLast() < x) {
que.pollLast();
}
que.addLast(x);
}
public int getMaxValue(Deque<Integer> que) {
return que.peekFirst();
}
}
347.前 K 个高频元素
思路:使用优先级队列(大顶堆或小顶堆)。定义map集合,将数组中元素存入key中,将元素出现的次数存入value中。定义一个小顶堆的优先级队列,队列中每个元素是一个长度为2的数组。遍历map集合,如果优先级队列大小小于k,则将map中该元素存入优先级队列,否则判断该元素的value是否大于优先级队列中的最小元素,如果大于则将优先级队列中最小元素弹出,并存放该元素到优先级队列中。
时间复杂度O(n*logk),因为优先级队列维护元素顺序的时间复杂度为O(logk)
/*Comparator接口说明:
* 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
* 对于队列:排在前面意味着往队头靠
* 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
* 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
* */
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 优先级队列(小顶堆)
// 在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
// 出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
Queue<int[]> p = new PriorityQueue<>((a, b) -> {
return a[1] - b[1];
});
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (p.size() < k) {
p.add(new int[]{entry.getKey(), entry.getValue()});
} else {
if (entry.getValue() > p.peek()[1]) {
p.poll();
p.add(new int[]{entry.getKey(), entry.getValue()});
}
}
}
int[] ans = new int[k];
for (int i = k - 1; i >= 0; i--) {
ans[i] = p.poll()[0];
}
return ans;
}
}
总结
栈的经典题目有:括号匹配问题、字符串去重问题、逆波兰式求值。 队列的经典题目有:滑动窗口最大值问题(使用自定义单调队列)、求前k个高频元素(使用优先级队列其实就是小顶堆大顶堆)