【代码随想录】栈与队列刷题

:先进后出

队列:先进先出

用栈实现队列

题目:232. 用栈实现队列 - 力扣(LeetCode)

核心思想:两个栈,一个负责入,一个负责出。

class MyQueue {
    
    
    Stack<Integer> a, b;
    
    public MyQueue() {
    
    
        a = new Stack<>();
        b = new Stack<>();
    }
    
    public void push(int x) {
    
    
        a.push(x);
    }
    
    public int pop() {
    
    
        if (!b.isEmpty()) return b.pop();
        while (!a.isEmpty()) b.push(a.pop());
        return b.pop();
    }
    
    public int peek() {
    
    
        if (!b.isEmpty()) return b.peek();
        while (!a.isEmpty()) b.push(a.pop());
        return b.peek();
    }
    
    public boolean empty() {
    
    
        return a.isEmpty() && b.isEmpty();
    }
}

数组模拟队列参考:【AcWing 学习】数据结构 + STL

用队列实现栈

题目:225. 用队列实现栈 - 力扣(LeetCode)

class MyStack {
    
    
    // a - 输入队列, b - 输出队列
    Queue<Integer> a, b;

    public MyStack() {
    
    
        a = new LinkedList<>();
        b = new LinkedList<>();
    }
    
    public void push(int x) {
    
    
        a.offer(x);
        // 将 b 队列中元素全部转给 a
        while (!b.isEmpty()) a.offer(b.poll());
        // 交换 a 和 b, 使得 a 在没有 push() 时永远为空队列
        Queue<Integer> tmp = a;
        a = b;
        b = tmp;
    }
    
    public int pop() {
    
    
        return b.poll();
    }
    
    public int top() {
    
    
        return b.peek();
    }
    
    public boolean empty() {
    
    
        return b.isEmpty();
    }
}

数组模拟栈参考:【AcWing 学习】数据结构 + STL

有效的括号

题目:20. 有效的括号 - 力扣(LeetCode)

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。
输入:s = "()[]{}"
输出:true
public boolean isValid(String s) {
    
    
	var map = Map.of('(', ')', '[', ']', '{', '}');
	Stack<Character> st = new Stack<>();
	for (char c : s.toCharArray()) {
    
    
		// 左括号入栈
		if (map.containsKey(c)) st.push(c);
		// 右括号, 判断是否与栈顶匹配
		else if (st.isEmpty() || c != map.get(st.pop())) return false;
	}
	return st.isEmpty();
}

删除字符串中的所有相邻重复项

题目:1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

利用栈模拟:

public String removeDuplicates(String s) {
    
    
	Deque<Character> st = new LinkedList<>();
	for (char c : s.toCharArray()) {
    
    
		if (!st.isEmpty() && st.peek() == c) st.pop();
		else st.push(c);
	}
	StringBuilder sb = new StringBuilder();
	while (!st.isEmpty()) sb.append(st.pop());
	return sb.reverse().toString();
}

利用 StringBuidler 实现栈的操作:

public String removeDuplicates(String s) {
    
    
	StringBuilder sb = new StringBuilder();
	for (char c : s.toCharArray()) {
    
    
		int len = sb.length();
		if (len > 0 && c == sb.charAt(len - 1)) sb.deleteCharAt(len - 1);
		else sb.append(c);
	}
	return sb.toString();
}

逆波兰表达式求值

题目:150. 逆波兰表达式求值 - 力扣(LeetCode)

根据 逆波兰表示法,求表达式的值。
有效的算符包括 +-*/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

没有括号参与的逆波兰表达式十分简单。

public int evalRPN(String[] tokens) {
    
    
    Deque<Integer> st = new ArrayDeque<>();
    for (String s : tokens) {
    
    
        if ("+".equals(s)) st.push(st.pop() + st.pop());
        else if ("-".equals(s)) st.push(-st.pop() + st.pop());
        else if ("*".equals(s)) st.push(st.pop() * st.pop());
        else if ("/".equals(s)) {
    
    
            int num1 = st.pop(), num2 = st.pop();
            st.push(num2 / num1);
        } else st.push(Integer.parseInt(s));
    }
    return st.pop();
}

滑动窗口的最大值*

题目:239. 滑动窗口最大值 - 力扣(LeetCode)

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

单调队列:永远保证队列头节点是最大值(或最小值)

public int[] maxSlidingWindow(int[] nums, int k) {
    
    
    int[] res = new int[nums.length - k + 1];
    LinkedList<Integer> q = new LinkedList<>(); // 队列中存下标
    int idx = 0;
    for (int i = 0; i < nums.length; i++) {
    
    
        // 队列头节点是最先放进来的, 需要检查在 [i - k + 1, i] 范围内
        if (!q.isEmpty() && i - k + 1 > q.peek()) q.poll();
        // 单调队列, 保证每次进去的数字比末尾的大
        while (!q.isEmpty() && nums[i] > nums[q.peekLast()]) q.pollLast();
        q.offer(i);
        // 当 i 符合 k 范围的时候, 每次队列头节点放入结果
        if (i >= k - 1) res[idx++] = nums[q.peek()];
    }
    return res;
}

前 K 个高频元素

题目:347. 前 K 个高频元素 - 力扣(LeetCode)

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

优先级队列<二元对<数字, 频率>> 大根堆

public int[] topKFrequent(int[] nums, int k) {
    
    
    Map<Integer, Integer> map = new HashMap<>();
    for (int n : nums) map.put(n, map.getOrDefault(n, 0) + 1);
    PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>(
            (o1, o2) -> o2.getValue() - o1.getValue()); // 大根堆
    for (var entry : map.entrySet()) pq.offer(entry);
    int[] res = new int[k];
    for (int i = 0; i < k; i++) res[i] = pq.poll().getKey();
    return res;
}

对 map 的 value 进行排序:

public int[] topKFrequent(int[] nums, int k) {
    
    
    Map<Integer, Integer> map = new HashMap<>();
    for (int n : nums) map.put(n, map.getOrDefault(n, 0) + 1);

    // 对 map 的 value 进行排序
    var list = new ArrayList<>(map.entrySet());
    Collections.sort(list, (o1, o2) -> o2.getValue() - o1.getValue());

    int[] res = new int[k];
    for (int i = 0; i < k; i++) res[i] = list.get(i).getKey();
    return res;
}

维护 size 为 k 的小根堆:

public int[] topKFrequent(int[] nums, int k) {
    
    
    Map<Integer, Integer> map = new HashMap<>();
    for (int n : nums) map.put(n, map.getOrDefault(n, 0) + 1);
    PriorityQueue<Integer> pq = new PriorityQueue<>(
		    (a, b) -> map.get(a) - map.get(b)); // 小根堆
    
    for (int n : map.keySet()) {
    
    
        if (pq.size() < k) pq.add(n); // size < k 则直接添加
		// size > k 则判断是否比堆顶元素大, 是则堆顶出堆, 将该元素入堆
        else if (map.get(n) > map.get(pq.peek())) {
    
    
            pq.poll();
            pq.add(n);
        }
    }
    int[] res = new int[k];
    for (int i = 0; i < k; i++) res[i] = pq.poll();
    return res;
}

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/126784843