代码随想录Day5--栈与队列专题

232 用栈实现队列

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

实现 MyQueue 类:

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

说明:

  • 你 只能 使用标准的栈操作 —— 也就是只有 push to toppeek/pop from topsize, 和 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();
 */

225 用队列实现栈

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

实现 MyStack 类:

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

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to backpeek/pop from frontsize 和 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();
 */

20 有效的括号

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

解题思路:可以使用栈来解决这个问题。遍历字符串,当遇到左括号时,将其加入栈中。当遇到右括号时,如果栈为空,说明没有对应的左括号,返回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();
    }
}

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

给出由小写字母组成的字符串 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();
    }
}

150 逆波兰表达式求值

给你一个字符串数组 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("/");  
    }
}

239 滑动窗口最大值

给你一个整数数组 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;  
    }  
}

347 前k个高频元素

给你一个整数数组 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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值