【java】【python】leetcode刷题记录--栈与队列

232 用栈实现队列

题目描述
两个栈模拟队列的思路是利用栈(后进先出结构)的特性来实现队列(先进先出结构)的行为。这种方法依赖于两个栈来逆转元素的入队和出队顺序,从而实现队列的功能。

入队操作(使用stackIn):所有新加入的元素都直接推入stackIn。因为栈支持后进先出,所以此时不需要考虑元素的顺序。

出队操作(使用stackOut):当需要进行出队操作(即移除队列的最前端元素)时,我们先检查stackOut:如果stackOut为空,则将stackIn中所有元素逐一弹出并推入stackOut。这样,最先进入stackIn的元素(也就是最早入队的元素)会位于stackOut的顶部。如果stackOut不为空,则直接从stackOut弹出顶部元素(队列的前端元素)。

通过这种方式,stackOut的栈顶始终保持为队列的最前端,而stackIn用于处理新的入队操作。

class MyQueue {

    Stack<Integer> stackIn;
    Stack<Integer> stackOut;

    public MyQueue() {
        stackIn = new Stack<>();
        stackOut = new Stack<>();
    }
    
    public void push(int x) {
        stackIn.push(x);
    }
    
    public int pop() {
        // 将in栈的内容全部转移到out栈,从out栈进行输出
        // 如果out栈有内容就先输出
        if(stackOut.empty()){
            while(!stackIn.empty()){
                stackOut.push(stackIn.pop());
            }
        }

        return stackOut.pop();
    }
    
    public int peek() {
        if(stackOut.empty()){
            while(!stackIn.empty()){
                stackOut.push(stackIn.pop());
            }
        }

        return stackOut.peek();
    }
    
    public boolean empty() {
        return (stackIn.empty())&&(stackOut.empty());
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

225 用队列实现栈

题目链接
这题也是利用两个队列来进行元素顺序的调整。

queue2是辅助队列,queue1存放进入栈的元素,当想要得到栈顶(队尾)元素,即把queue1的元素放入queue2,知道queue1只剩一个元素,该元素则为栈顶元素。将其弹出即可。剩余操作也是类似。

class MyStack {

    Queue<Integer> queue1;
    Queue<Integer> queue2;

    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }
    
    public void push(int x) {
        queue2.offer(x); // 先放在辅助队列中
        while (!queue1.isEmpty()){
            queue2.offer(queue1.poll());
        }
        Queue<Integer> queueTemp;
        queueTemp = queue1;
        queue1 = queue2;
        queue2 = queueTemp; // 最后交换queue1和queue2,将元素都放到queue1中
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return queue1.poll(); // 因为queue1中的元素和栈中的保持一致,所以这个和下面两个的操作只看queue1即可
    }
    
    /** Get the top element. */
    public int top() {
        return queue1.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue1.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

python版本:

from queue import Queue

class MyStack:
    def __init__(self):
        self.queue1 = Queue()

    def push(self, x):
        # 临时队列,用于转移元素
        temp_queue = Queue()
        temp_queue.put(x)  # 先放入新元素(栈顶元素)
        # 将原队列中的元素转移到临时队列中,确保新元素始终在队列头部
        while not self.queue1.empty():
            temp_queue.put(self.queue1.get())
        self.queue1 = temp_queue  # 更新队列为新的队列

    def pop(self):
        # 直接从 queue1 中取出元素,因为 queue1 的队头是栈顶
        return self.queue1.get()

    def top(self):
        # 获取队头元素即栈顶元素
        top_element = self.queue1.get()
        # 为保持队列状态,将该元素重新放回队头
        temp_queue = Queue()
        temp_queue.put(top_element)
        while not self.queue1.empty():
            temp_queue.put(self.queue1.get())
        self.queue1 = temp_queue  # 更新队列
        return top_element

    def empty(self):
        # 如果 queue1 为空,则栈为空
        return self.queue1.empty()

20 有效的括号

题目描述
很经典的栈的题目。

如果遇到左括号则要入栈,遇到右括号则与栈顶的元素配对,配对失败则是false,反之继续配对。这里要特别注意,右括号来的适合左括号可能为空,这是false。或者最后左括号剩余,这也是false。

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();

        for(int i=0; i<s.length(); i++){
            char tmp = s.charAt(i);
            if (tmp == '(' || tmp == '{' || tmp == '[') {
                stack.push(tmp);
            } else {
                if (stack.empty()) return false; // 先检查栈是否为空
                    char top = stack.pop(); // 弹出栈顶元素以匹配
                    if (tmp == ')' && top != '(') return false;
                    if (tmp == '}' && top != '{') return false;
                    if (tmp == ']' && top != '[') return false;
                }
        }
        return stack.empty();
        
    }
}

python版本:

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []

        for char in s:
            if char in '({[':
                stack.append(char)
            else:
                if not stack:
                    return False  # 检查栈是否为空
                top = stack.pop()  # 弹出栈顶元素以匹配
                if char == ')' and top != '(':
                    return False
                if char == '}' and top != '{':
                    return False
                if char == ']' and top != '[':
                    return False

        return not stack  # 栈空则有效,非空则无效

当然,这题也可以用set(map)进行查找的优化,但意义不太大。比如如下代码:


import java.util.HashMap;
import java.util.Stack;

public class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        HashMap<Character, Character> map = new HashMap<>();

        // 存储括号对应关系
        map.put(')', '(');
        map.put('}', '{');
        map.put(']', '[');

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 如果是右括号
            if (map.containsKey(c)) {
                // 栈为空或栈顶元素不匹配当前右括号对应的左括号
                if (stack.isEmpty() || stack.pop() != map.get(c)) {
                    return false;
                }
            } else {
                // 否则为左括号,压入栈中
                stack.push(c);
            }
        }
        // 如果栈为空,说明所有括号都匹配成功
        return stack.isEmpty();
    }
}

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

题目链接
第一眼还以为要双指针或者滑动窗口,但并不用,双指针往往是对数组/字符串/链表进行操作,滑动窗口则是找子序列/最大长度这种。

这题实际上就是栈的应用,没遇到一个新元素就入栈,如果栈顶元素与新的元素相同,则把栈顶元素出栈,以此类推。

关于java的StringBuider,看这篇:链接

class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();

        for(int i=0; i<s.length(); i++){
            char tmp = s.charAt(i);
            if(!stack.isEmpty() && stack.peek()==tmp){
                stack.pop();
            }else{
                stack.push(tmp);
            }
        }

        StringBuilder res = new StringBuilder();
        for (char ch : stack) {
            res.append(ch);
        }

        return res.toString();
    }
}

python版本:join可以方便的把列表转换为字符串。如果不用join那会浪费一些时间。

class Solution:
    def removeDuplicates(self, s: str) -> str:
        stack = []

        for char in s:
            if stack and stack[-1] == char:
                stack.pop()
            else:
                stack.append(char)

        return ''.join(stack)
        #或者这样
        res = ''
        for c in stack:
            res = res + c
        return res

150 逆波兰表达式求值

题目链接
题目很简单,如果了解后缀表达式很轻松能写出来,将数字存在栈中,遇到符号取出栈顶的2个元素计算,再将结果放回栈内即可。

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();

         for (String token : tokens) {
            if (token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")) {
                int b = stack.pop();  // 先弹出的是第二个操作数
                int a = stack.pop();  // 再弹出的是第一个操作数
                switch (token) {
                    case "+":
                        stack.push(a + b);
                        break;
                    case "-":
                        stack.push(a - b);
                        break;
                    case "*":
                        stack.push(a * b);
                        break;
                    case "/":
                        stack.push(a / b);
                        break;
                }
            } else {
                // 直接将字符串转换为整数并压栈
                stack.push(Integer.parseInt(token));
            }
        }
        // 最终栈顶元素就是表达式的结果
        return stack.peek();
    }
}

python版本:

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        res = []
        print(int(6/(-132)))
        for token in tokens:
            if token not in {'+', '-', '*', '/'}:
                res.append(int(token))
            else:
                a = res.pop()
                b = res.pop()
                
                if token == '+':
                    res.append(a+b)
                elif token == '-':
                    res.append(b-a)
                elif token == '*':
                    res.append(a*b)
                elif token == '/':
                    res.append(int(b/a))
                    
        return res[0]

在Python中,对于整数除法,/ 操作符执行的是真除法(返回浮点结果),而 // 操作符执行的是地板除(即对结果向下取整到最近的整数)。因此,当使用 / 并将结果强制转换为 int 时,它只是简单地去掉了小数部分,不进行四舍五入,而且对于负数结果也只是截断小数部分。而使用 //,则是在计算结果后直接返回一个整数,且结果总是向下取整,这种方式与C++和Java中的整数除法一致。

对于正数除法:

  • 5 / 2 结果为 2.5,int(5 / 2) 结果为 2
  • 5 // 2 结果为 2。

对于负数除法:

  • -5 / 2 结果为 -2.5,int(-5 / 2) 结果为 -2。
  • -5 // 2 结果为 -3,因为 -2.5 向下取整是 -3。

因此这里要使用转换为int,而不是//。

239 滑动窗口最大值

题目描述
如果直接做这个题,不难发现时间复杂度是O(n*k),即遍历+遍历窗口即可。

但又没有能够线性时间内完成的方法呢?

单调队列是一种特殊的队列数据结构,其主要特点是队列中的元素单调递增或单调递减。这种队列在处理滑动窗口类型的问题时非常有用,能够高效地维护一个固定大小的窗口内最大值或最小值的集合。
单调队列的主要应用场景是解决滑动窗口类型的问题,例如:

  • 查找滑动窗口中的最大值或最小值:给定一个数组和一个窗口大小,找出每个窗口内的最大值或最小值。
  • 动态维护数据流中的最大值或最小值:在数据流不断到来时,动态更新当前窗口内的最大值或最小值。
  • 优化某些动态规划问题:在动态规划问题中,如果状态转移涉及到一个固定大小的窗口,可以使用单调队列优化计算过程

那么怎么实现一个单调队列呢?

我们可以使用linkedlist来实现:

public class MonotonicQueue {
    private LinkedList<int[]> deque;
	//每个 int[] 数组包含两个元素:
	//index:元素在原数组中的位置。
	//value:元素的实际值。
    public MonotonicQueue() {
        deque = new LinkedList<>();
    }

    public void push(int index, int value) {
        // 维护队列的单调递减性
        while (!deque.isEmpty() && value > deque.getLast()[1]) {
        	// 如果大于队列末尾的值,则队尾值出队列,为value入队做铺垫
            deque.removeLast();
        }
        deque.addLast(new int[]{index, value});
    }

    public void pop(int index) {
        // 移除队列中过期的元素
        if (!deque.isEmpty() && deque.getFirst()[0] == index) {
            deque.removeFirst();
        }
    }

    public int max() {
        // 获取队列中的最大值
        return deque.isEmpty() ? Integer.MIN_VALUE : deque.getFirst()[1];
    }
}

实现单调队列后,就可以处理窗口的最大值。我们的单调队列里只保存窗口的最大值,窗口滑动时,队列里的值也会发生改动。因此,最后的代码为:

class MonotonicQueue {
    private LinkedList<int[]> deque;
	//每个 int[] 数组包含两个元素:
	//index:元素在原数组中的位置。
	//value:元素的实际值。
    public MonotonicQueue() {
        deque = new LinkedList<>();
    }

    public void push(int index, int value) {
        // 维护队列的单调递减性
        while (!deque.isEmpty() && value > deque.getLast()[1]) {
        	// 如果大于队列末尾的值,则队尾值出队列,为value入队做铺垫
            deque.removeLast();
        }
        deque.addLast(new int[]{index, value});
    }

    public void pop(int index) {
        // 移除队列中过期的元素
        if (!deque.isEmpty() && deque.getFirst()[0] == index) {
            deque.removeFirst();
        }
    }

    public int max() {
        // 获取队列中的最大值
        return deque.isEmpty() ? Integer.MIN_VALUE : deque.getFirst()[1];
    }
}

public class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MonotonicQueue mq = new MonotonicQueue();
        int[] result = new int[nums.length - k + 1];
        for (int i = 0; i < nums.length; i++) {
            mq.push(i, nums[i]);
            if (i >= k - 1) {
                result[i - k + 1] = mq.max();
                mq.pop(i - k + 1);
            }
        }
        return result;
    }
}

python版本:

from collections import deque

class MonotonicQueue:
    def __init__(self):
        self.deque = deque()

    def push(self, index, value):
        # 维护队列的单调递减性
        while self.deque and value > self.deque[-1][1]:
            self.deque.pop()
        self.deque.append((index, value))

    def pop(self, index):
        # 移除队列中过期的元素
        if self.deque and self.deque[0][0] == index:
            self.deque.popleft()

    def max(self):
        # 获取队列中的最大值
        return self.deque[0][1] if self.deque else float('-inf')

class Solution:
    def maxSlidingWindow(self, nums, k):
        mq = MonotonicQueue()
        result = []
        for i, num in enumerate(nums):
            mq.push(i, num)
            if i >= k - 1:
                result.append(mq.max())
                mq.pop(i - k + 1)
        return result

347 前k个高频元素

题目描述
题目思路很简单,可以用哈希表存储数字和频率,进而找到前k个频率输出即可。

如果不会用复杂的数据结构(比如堆),那可以用二维数组:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> frequencyMap = new HashMap<>();
        for (int num : nums) {
            frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);
        }

        // 创建二维数组,一行存储一个数字及其频率
        int[][] freqArray = new int[frequencyMap.size()][2];
        int index = 0;
        for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
            freqArray[index][0] = entry.getKey();
            freqArray[index][1] = entry.getValue();
            index++;
        }

        // 根据频率对二维数组进行排序
        Arrays.sort(freqArray, (a, b) -> b[1] - a[1]);

        // 从排序后的数组中取出前k个数字
        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = freqArray[i][0];
        }

        return result;
    }
}

但免不了的,这样效率很低下,所以我们想一想有没有别的办法。我们都知道,如果要找前k大/小的元素,用堆排序是很方便的。那么对于这题来说,不过是把找大小变为了频率的高低,其实也是找大小。

堆(Heap) 是一种完全二叉树,它满足两个特性:

  1. 结构性:堆是一个完全二叉树,即除了最后一层外,每一层都被完全填满,最后一层的节点从左向右填入。
  2. 堆序性:对于每个节点i,除了根节点外,都有父节点i/2(向下取整)。对于最小堆,每个节点的值都小于或等于其子节点的值;对于最大堆,每个节点的值都大于或等于其子节点的值。

在java中,PriorityQueue类实现了堆的数据结构,并且提供了操作堆的方法。默认情况下,PriorityQueue是一个最小堆,这意味着队列中的最小元素会优先被取出。具体内容可以看链接

这里还有一个很关键的问题:我们的思路是用hashmap存储数字和对应的频率,然后用堆来筛选频率前k高的元素,那么我们要使用大根堆还是小根堆呢?

也许你会不假思索认为是大根堆,因为我们要保留的是前k大的元素,好像大根堆的性质更符合。但实际上不是这样的:如果用大根堆,我们只能确定最大值,对于根节点(最大值)的左右孩子谁更大这点不得而知。也许左子树的根节点小于右子树的根节点,同时也小于右子树的根节点的子节点,这样我们无法确认前k大的值是谁,我们只能很方便的确定最大值。如果大根堆目前有5个值,我们也要找前5大的频率,此时又来了一个值,我们需要遍历全部值才知道谁是最小的,然后将其剔除,这显而易见是很麻烦的。

如果用小根堆呢?小根堆我们每次都能精确的找到最小的值,我们也就不需要在剔除最小值是犯难了,而一次操作的时间复杂度也就是删除堆元素的时间复杂度而已。这无疑会优化我们的时间复杂度。

我们可以总结出如下的内容:

  1. 使用大根堆(Max Heap):
  • 用于筛选或保留最大值。
  • 常见于需要频繁访问或删除最大值的场景(保留小值)。
  1. 使用小根堆(Min Heap):
  • 用于筛选或保留最小值。
  • 常见于需要频繁访问或删除最小值的场景(保留大值)。

因此,代码如下:

import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> frequencyMap = new HashMap<>();
        for (int num : nums) {
            frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);
        }

        PriorityQueue<Map.Entry<Integer, Integer>> minHeap = new PriorityQueue<>(
            (a, b) -> a.getValue() - b.getValue()
        );

        for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
            minHeap.add(entry);
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }

        int[] result = new int[k];
        for (int i = k - 1; i >= 0; i--) {
            result[i] = minHeap.poll().getKey();
        }

        return result;
    }
}

如果用python就是这样:

import heapq
from collections import Counter

class Solution:
    def topKFrequent(self, nums, k):
        # 统计每个元素的频率
        frequency_map = Counter(nums)

        # 使用最小堆来保持频率最高的前k个元素
        min_heap = []

        for num, freq in frequency_map.items():
            heapq.heappush(min_heap, (freq, num))
            if len(min_heap) > k:
                heapq.heappop(min_heap)

        # 从最小堆中取出元素
        result = [heapq.heappop(min_heap)[1] for _ in range(k)]
        result.reverse()

        return result


  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值