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

104 阅读3分钟

150. 逆波兰表达式求值

题目链接: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. 滑动窗口最大值

题目链接: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 个高频元素

题目链接: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个高频元素(使用优先级队列其实就是小顶堆大顶堆)