请你仅使用两个栈实现先入先出(FIFO)队列。队列应当支持一般队列支持的所有操作(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(双端队列)来模拟一个栈,只要是标准的栈操作即可。
解题思路:使用两个栈来模拟队列的操作。其中一个栈用于入队操作,另一个栈用于出队操作。当进行出队操作时,如果输出栈为空,就将输入栈中的元素逐个弹出并压入输出栈中,这样输出栈的栈顶元素就是队列中的第一个元素。
class MyQueue {
private Stack<Integer> inputStack;
private Stack<Integer> outputStack;
public MyQueue() {
inputStack=new Stack<>();
outputStack=new Stack<>();
}
public void push(int x) {
inputStack.push(x);
}
public int pop() {//返回栈顶的值后,删除栈顶的值
if(outputStack.isEmpty()){
while(!inputStack.isEmpty())
outputStack.push(inputStack.pop());
}
return outputStack.pop();
}
public int peek() {//返回栈顶的值后,但不删除栈顶的值
if(outputStack.isEmpty()){
while(!inputStack.isEmpty())
outputStack.push(inputStack.pop());
}
return outputStack.peek();
}
public boolean empty() {
return inputStack.isEmpty() && outputStack.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(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
解题思路:使用两个队列 q1
和 q2
来实现栈的操作。其中,push
操作直接将元素加入 q1
队列即可。pop
和 top
操作都需要将除最后一个元素外的其他元素转移到 q2
队列中,然后将最后一个元素出队并返回。最后,交换 q1
和 q2
的引用,使得下一次操作可以从新的队列开始。empty
操作只需要判断 q1
是否为空即可。
class MyStack {
private Queue<Integer> q1;
private Queue<Integer> q2;
public MyStack() {
q1=new LinkedList<>();
q2=new LinkedList<>();
}
public void push(int x) {
q1.add(x);
}
public int pop() {//pop是获得栈顶元素
if(q1.isEmpty()) return -1;
int size=q1.size();
for(int i=0;i<size-1;i++){
q2.add(q1.poll()); //poll()方法用于检索链表的第一个或初始元素,并从列表中删除第一个元素
}
int top=q1.poll();
Queue<Integer> temp;
temp=q1;
q1=q2;
q2=temp;
return top;
}
public int top() {//top是获得栈顶元素,不弹出
if(q1.isEmpty()) return -1;
int size=q1.size();
for(int i=0;i<size-1;i++){
q2.add(q1.poll());
}
int top=q1.poll();
q2.add(top);
Queue<Integer> temp;
temp=q1;
q1=q2;
q2=temp;
return top;
}
public boolean empty() {
return q1.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();
*/
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
解题思路:可以使用栈来解决这个问题。遍历字符串,当遇到左括号时,将其加入栈中。当遇到右括号时,如果栈为空,说明没有对应的左括号,返回false;否则,将栈顶元素出栈,并判断它是否与当前右括号匹配,如果不匹配,返回false。最后,如果栈为空,说明所有括号都匹配成功,返回true;否则,返回false。
class Solution {
public boolean isValid(String s) {
Stack<Character> stack=new Stack();
for(char c:s.toCharArray()){
if(c=='('||c=='['||c=='{'){
stack.push(c);
}else if(c==')'||c==']'||c=='}'){
if(stack.isEmpty())
return false;
char top=stack.pop();
//if((c==')'&&top=='(')||(c==']'&&top=='[')||(c=='}'&&top=='{'))
// return true; //这两行这样写执行不通过
if((c==')'&&top!='(')||(c==']'&&top!='[')||(c=='}'&&top!='{'))
return false;
}
}
return stack.isEmpty();
}
}
给出由小写字母组成的字符串 S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
解题思路:使用栈数据结构来模拟重复删除操作。我们遍历字符串中的每个字符,如果当前字符与栈顶元素相同,我们就弹出栈顶元素,并继续下一个字符。否则,我们将当前字符推入栈中。最后,我们将栈中剩余的元素连接起来形成最终的字符串。
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack=new Stack();
for(char c:s.toCharArray()){
if(!stack.isEmpty()&&stack.peek()==c){
stack.pop();
}else{
stack.push(c);
}
}
StringBuilder sb=new StringBuilder();
for(char c:stack){
sb.append(c);
}
return sb.toString();
}
}
给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
解题思路:逆波兰表达式,也被称为后缀表达式,是一种没有括号的算术表达式。在这种表示法中,所有操作数都在操作符之前。例如,表达式 "2 3 +" 等于 5。
在这个函数中,我们首先创建一个空的栈。然后,对于输入数组中的每个元素,如果它是一个操作符,我们就从栈中弹出两个元素,对它们执行相应的操作,然后将结果推回栈中。如果它是一个数字,我们就直接将其推入栈中。最后,栈中的唯一元素就是表达式的结果。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (String token : tokens) {
if (isOperator(token)) {
int num2 = stack.pop();
int num1 = stack.pop();
switch (token) {
case "+":
stack.push(num1 + num2);
break;
case "-":
stack.push(num1 - num2);
break;
case "*":
stack.push(num1 * num2);
break;
case "/":
stack.push(num1 / num2); //不能写成stack.push(num1/num2);
break;
}
} else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
private boolean isOperator(String op) {
return op.equals("+") || op.equals("-") || op.equals("*") || op.equals("/");
}
}
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
解题思路:使用双端队列来实现,队列中存储的是数组中的下标,每次遍历到一个新的数,首先判断队首的下标是否在窗口内,如果不在则将其弹出队列。然后将队列尾部所有下标对应的数与新数比较,如果比新数小则将其弹出队列。然后将新数加入队列,此时队列的队首即为当前窗口中的最大值。
import java.util.*;
public class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return new int[0];
}
int n = nums.length;
int[] res = new int[n - k + 1];
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 如果队首下标不在窗口内,则弹出队首
if (!deque.isEmpty() && deque.peekFirst() == i - k) {
deque.pollFirst();
}
// 将队列尾部所有下标对应的数与新数比较,如果比新数小则将其弹出队列
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) { //peekLast()获取队尾元素但不移除,如果队列无元素,则返回null
deque.pollLast(); //pollLast()返回并移除队头元素,如果队列无元素,则返回null
}
// 将新数加入队列
deque.offer(i); //java.util.ArrayDeque.offer(E e)此方法表示在双端队列的末尾插入指定的元素
// 如果当前窗口已经形成,则记录最大值
if (i >= k - 1) {
res[i - k + 1] = nums[deque.peekFirst()];
}
}
return res;
}
}
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
解题思路:
可以使用哈希表(HashMap)和优先队列(PriorityQueue)来实现。
首先,使用 HashMap 统计每个元素出现的频率,并将其存入 PriorityQueue 中。PriorityQueue 中每个元素的优先级按照其出现频率从大到小排序。然后,循环 k 次,每次从 PriorityQueue 中取出队首元素(即出现频率最高的元素),将其加入结果数组中,并将其从 HashMap 中移除。最后返回结果数组即可。
class Solution {
//PriorityQueue是一种基于优先级堆的极大优先级队列,默认情况下PriorityQueue是小顶堆,每次从队列中取出的是优先权小的元素
//方法一:大根堆
public int[] topKFrequent(int[] nums, int k) {
// 统计每个元素出现的频率
Map<Integer,Integer> freqMap=new HashMap<>();
for(int num:nums){
freqMap.put(num,freqMap.getOrDefault(num,0)+1);
}
// 将元素加入优先队列,按照频率从大到小排序
PriorityQueue<Integer> pq=new PriorityQueue<>((a,b)->freqMap.get(b)-freqMap.get(a));
for(int num:freqMap.keySet()){
pq.offer(num);
}
int[] result=new int[k];
// 循环 k 次,取出队首元素并加入结果数组
for(int i=0;i<k;i++){
result[i]=pq.poll();
//freqMap.remove(result[i]);
}
return result;
}
}
//方法二:小顶堆
/*Comparator接口说明:
* 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
* 对于队列:排在前面意味着往队头靠
* 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
* 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
* */
class Solution {
//解法1:基于大顶堆实现
public int[] topKFrequent1(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从大到小排,出现次数最多的在队头(相当于大顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1]-pair1[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){//大顶堆需要对所有元素进行排序
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
int[] ans = new int[k];
for(int i=0;i<k;i++){//依次从队头弹出k个,就是出现频率前k高的元素
ans[i] = pq.poll()[0];
}
return ans;
}
//解法2:基于小顶堆实现
public int[] topKFrequent2(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆只需要维持k个元素有序
if(pq.size()<k){//小顶堆元素个数小于k个时直接加
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
if(entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] ans = new int[k];
for(int i=k-1;i>=0;i--){//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
ans[i] = pq.poll()[0];
}
return ans;
}
}