匹配问题都是栈的强项,当题目设计匹配问题时,可以考虑使用栈来实现。
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(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();
*/
请你仅使用两个队列实现一个后入先出(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();
*/
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 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();
}
}
给出由小写字母组成的字符串 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;
}
}
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 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();
}
}
给你一个整数数组 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 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 |
示例 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();
}
}
给你一个整数数组 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;
}
}
给你一个字符串 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();
}
}