leetcode刷题(栈与队列)

本文探讨如何运用栈和队列数据结构实现匹配问题的解决方案,包括用两个栈模拟队列的操作,以及如何用队列构建栈。实例涵盖队列和栈的基本操作,如先入先出和后入先出。同时,还介绍了如何在特定场景下优化操作时间复杂度。
摘要由CSDN通过智能技术生成

匹配问题都是栈的强项,当题目设计匹配问题时,可以考虑使用栈来实现。

  1. 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek()返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty
操作是合法的。 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

进阶:

你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n)
,即使其中一个操作可能花费较长时间。

示例:
输入: [“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出: [null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

# python
class MyQueue:
    # 创建两个栈结构,in负责push,out负责pop,即可保证先进先出
    def __init__(self):
        self.stack_in = []
        self.stack_out = []

    def push(self, x: int) -> None:
        self.stack_in.append(x)

    def pop(self) -> int:
        if self.empty():
            return None
        if self.stack_out:
            res = self.stack_out.pop()
        else:
            for i in range(len(self.stack_in)):
                self.stack_out.append(self.stack_in.pop())
            res = self.stack_out.pop()
        return res


    def peek(self) -> int:
        res = self.pop()
        self.stack_out.append(res)
        return res


    def empty(self) -> bool:
        return not(self.stack_in or self.stack_out)

# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
// Java
class MyQueue {
    // Deque是一个双端队列接口,继承自Queue接口
    // Deque的实现类是LinkedList、ArrayDeque、LinkedBlockingDeque,其中LinkedList是最常用的。
    // Java堆栈Stack类已经过时,Java官方推荐使用Deque替代Stack使用。Deque堆栈操作方法:push()、pop()、peek()。
    Deque<Integer> inStack;
    Deque<Integer> outStack;
    public MyQueue() {
        inStack = new LinkedList<Integer>();
        outStack = new LinkedList<Integer>();

    }
    
    public void push(int x) {
        inStack.push(x);
    }
    
    public int pop() {
        if (outStack.isEmpty()) {
            while (!inStack.isEmpty()) {
                outStack.push(inStack.pop());
            }
        } 
        return outStack.pop();
    }
    
    public int peek() {
        int res = pop();
        outStack.push(res);
        return res;
    }
    
    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }
}

/**
 * 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();
 */
  1. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:
输入: [“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出: [null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回False

# python
# 优化,仅使用一个deque实现
from collections import deque
class MyStack:
    def __init__(self):
        self.que1 = deque()

    def push(self, x: int) -> None:
        self.que1.append(x)

    def pop(self) -> int:
        # if self.empty():
        #    return None
        for i in range(len(self.que1) - 1):		# 将前面n-1个数移到后面
            self.que1.append(self.que1.popleft())	# 如1234, 移动后变成4123
        return self.que1.popleft()	# 再把左边的数弹出就得到栈顶元素4

    def top(self) -> int:
        if self.empty():
            return None
        return self.que1[-1]

    def empty(self) -> bool:
        return len(self.que1) == 0


# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
// Java
// 优化,仅使用一个Deque实现
class MyStack {
    Deque<Integer> que1;
    public MyStack() {
        que1 = new ArrayDeque(); 
    }
    
    public void push(int x) {
        que1.addLast(x);
    }
    
    public int pop() {
        for (int i = 0; i < que1.size() - 1; i++) {
            que1.addLast(que1.pollFirst());
        }
        return que1.pollFirst();	// 返回第一个元素,并删除该元素,如果此队列为空,则返回null
    }
    
    public int top() {
        return que1.peekLast();		// 返回最后一个元素,不删除元素
    }
    
    public boolean empty() {
        return que1.size() == 0;
    }
}

/**
 * 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();
 */
  1. 有效的括号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1:
输入:s = “()”
输出:true

示例 2:
输入:s = “()[]{}”
输出:true

示例 3:
输入:s = “(]”
输出:false

# python
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        for i in range(len(s)):
            if s[i] == '(':
                stack.append(')')
            elif s[i] == '{':
                stack.append('}')
            elif s[i] == '[':
                stack.append(']')
            # 剩下为出现右括号的情况
            # 如果未遍历完字符串,栈为空或者栈顶元素与右括号不匹配,返回False
            elif not stack or s[i] != stack[-1]:
                return False
            # 如果栈未空且匹配,出栈
            else:
                stack.pop()
        # 遍历完字符串,如果栈不为空,说明有左括号没有被匹配完
        return not stack
// Java
class Solution {
    public boolean isValid(String s) {
        Deque<Character> stack = new LinkedList<>();
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push(')');
            } else if (s.charAt(i) == '{') {
                stack.push('}');
            } else if (s.charAt(i) == '[') {
                stack.push(']');
            // 剩下为出现右括号的情况
            // 如果栈为空或者栈顶元素与右括号不匹配,返回False
            } else if (stack.isEmpty() || s.charAt(i) != stack.peek()) {
                return false;
            // 如果匹配,出栈
            } else {        
                stack.pop();
            }
        }
        // 遍历完字符串,如果栈不为空,说明有左括号没有被匹配完
        return stack.isEmpty();
    }
}
  1. 删除字符串中的所有相邻重复项

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

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

# python
class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = []
        for i in s:
            if res and i == res[-1]:
                res.pop()
            # res没有元素或者栈顶元素与i不相等,进栈
            else:
                res.append(i)
        return ''.join(res)
// Java
class Solution {
    public String removeDuplicates(String s) {
        Deque<Character> res = new ArrayDeque<>();
        char c;
        for (int i = 0; i < s.length(); i++) {
            c = s.charAt(i);
            // res没有元素或者栈顶元素与i不相等,进栈
            if (res.isEmpty() || res.peek() != c) {
                res.push(c);
            } else {
                res.pop();
            }
        }
        String str = "";
        // for (char j : res) {
        //     str = j + str;
        // }
        while (!res.isEmpty()) {
            str = res.pop() + str;
        }
        return str;
    }
}
  1. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

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

# python
class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        res = []
        for i in tokens:
            if i in ['+', '-', '*', '/']:
                second, first = res.pop(), res.pop()
                # {} 充当占位符, f用来格式化字符串,包含的{}表达式在程序运行时会被表达式的值代替
                # 因为题目要求只保留整数部分,所以用int转换结果类型
                res.append(int(eval(f'{first}{i}{second}'))) 	# eval:将字符串str当成有效的表达式来求值并返回计算结果
            else:
                res.append(i)
        # tokens为字符串类表,若只有一个元素“18”,则需将其转成int型
        return int(res[0])
// Java
class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> res = new LinkedList<>();
        // 先判断字符串是不是操作符,不是则正常入栈,是则取出栈顶的两个元素进行相应操作
        String[] opList = {"+", "-", "*", "/"};
        for (String s : tokens) {
            if (!Arrays.asList(opList).contains(s)) {   // 将字符串数组使用Arrays的asList()将数组变成集合,再使用集合中的contains方法
                res.push(Integer.valueOf(s));   // 将字符串s转化为对应的Integer类,后面出栈可以直接计算
            } else if (s.equals("+")) {			// 注意这里用equals比较,leetcode上用==出来结果不一样
                res.push(res.pop() + res.pop());
            } else if (s.equals("-")) {
                res.push(- res.pop() + res.pop());
            } else if (s.equals("*")) {
                res.push(res.pop() * res.pop());
            } else {
                int second = res.pop();
                int first = res.pop();
                res.push(first/second);
            }
        }
        // 全部计算完后栈内元素只剩一个,即最终计算结果
        return res.pop();
    }
}
  1. 滑动窗口最大值

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

返回滑动窗口中的最大值。

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]

解释:

滑动窗口的位置最大值
[1 3 -1] -3 5 3 6 73
1 [3 -1 -3] 5 3 6 73
1 3 [-1 -3 5] 3 6 75
1 3 -1 [-3 5 3] 6 75
1 3 -1 -3 [5 3 6] 76
1 3 -1 -3 5 [3 6 7]7

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

# python
class Solution(object):
    def maxSlidingWindow(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        res = []
        que = myDeque()
        for i in range(k):
            que.push(nums[i])
        res.append(que.front())
        for j in range(k, len(nums)):
            que.pop(nums[j-k])
            que.push(nums[j])
            res.append(que.front())
        return res
class myDeque(object):
    # 用额外的单调递减的que存放可能为滑动窗口中最大值的数,最左的数为最大值
    def __init__(self):
        # self.que = []
        self.que = collections.deque()
    # 如果滑动窗口要删除的数为que的第一个,则把最左边的最大值弹出
    # 如果滑动窗口要删除的数不是que的第一个,说明删掉的不是最大值,窗口最大值仍为第一个
    def pop(self, value):
        if self.que and value == self.que[0]:
            # self.que.pop(0)
            self.que.popleft()  # list.pop()时间复杂度为O(n),这里使用collections.deque()
    # 把que中从右边起小于要入栈的元素删掉,当前元素入栈后,小于该元素的不可能为最大值
    def push(self, value):
        while self.que and value > self.que[-1]:
            self.que.pop()
        self.que.append(value)
    # 用front函数返回当前窗口最大值
    def front(self):
        return self.que[0]
// Java
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] res = new int[nums.length - k + 1];
        int index = 0;
        MyDeque que = new MyDeque();
        for (int i = 0; i < k; i++) {
            que.push(nums[i]);
        }
        res[index++] = que.front();
        for (int j = k; j < nums.length; j++) {
            que.pop(nums[j - k]);
            que.push(nums[j]);
            res[index++] = que.front();
        }
        return res;
    }
}
// LinkedList 实现了Deque 接口,即能将LinkedList当作双端队列使用
class MyDeque {
    Deque<Integer> que;
    public MyDeque() {
        que = new LinkedList();
    }
    public void pop(int value) {
        if (!que.isEmpty() && value == que.peekFirst()) {   // 返回头部元素
            que.pollFirst();    // 删除队头元素
        }
    }
    public void push(int value) {
        while (!que.isEmpty() && value > que.peekLast()) {
            que.pollLast();     // 删除队尾元素
        }
        que.offerLast(value);   // 向链表末尾添加元素
    }
    public int front() {
        return que.peekFirst();
    }
}
  1. 前K个高频元素

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

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

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

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

# python
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # 统计元素出现频率
        map = {}
        for i in nums:
            map[i] = map.get(i, 0) + 1
        
        #定义一个小顶堆,对频率排序
        pri_que = []

        for key, pred in map.items():
            heapq.heappush(pri_que, (pred, key))
        # 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
        while len(pri_que) > k:
            heapq.heappop(pri_que)

        # 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒叙来输出到数组
        res = [0] * k
        for i in range(k - 1, -1, -1):
            res[i] = heapq.heappop(pri_que)[1]
        return res
// Java
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        
        Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
        // PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口), 比较两个元素的大小(较小的在前)
        PriorityQueue<Map.Entry<Integer, Integer>> priQue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
        for (Map.Entry<Integer, Integer> entry : entries) {
            priQue.offer(entry);
        }
        while (priQue.size() > k) {
            priQue.poll();
        }

        int[] res = new int[k];
        for (int i = k - 1; i >= 0; i--) {
            res[i] = priQue.poll().getKey();
        }
        return res;
    }
}
  1. 简化路径

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为更加简洁的规范路径。

在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//')都被视为单个斜杠 '/' 。 对于此问题,任何其他格式的点(例如,'...')均被视为文件/目录名称。

请注意,返回的 规范路径 必须遵循下述格式:

  • 始终以斜杠 '/' 开头。
  • 两个目录名之间必须只有一个斜杠 '/'
  • 最后一个目录名(如果存在)不能 以 '/' 结尾。
  • 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.''..')。

返回简化后得到的 规范路径 。

示例 1:
输入:path = “/home/”
输出:“/home”
解释:注意,最后一个目录名后面没有斜杠。

示例 2:
输入:path = “/../”
输出:“/”
解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。

示例 3:
输入:path = “/home//foo/”
输出:“/home/foo”
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。

示例4:
输入:path = “/a/./b/../../c/”
输出:“/c”

# python
class Solution:
    def simplifyPath(self, path: str) -> str:
        stack = []
        for p in path.split('/'):
            if stack and p == '..':
                stack.pop()
            elif p not in '..':
                stack.append(p)
        return '/'+'/'.join(stack)
// Java
class Solution {
    public String simplifyPath(String path) {
        Stack<String> stack = new Stack<>();
        StringBuilder res = new StringBuilder();
        for (String p : path.split("/")) {
            if (!stack.isEmpty() && p.equals("..")) {
                stack.pop();
            } else if (!"..".contains(p)) {
                stack.push(p);
            }
        }
        for (String i : stack) {
            res.append("/" + i);
        }
        return res.length() == 0 ? "/" : res.toString();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天涯小才

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值