【leetcode刷题记录】(java)栈 队列

建议代码随想录至少二刷

232. 用栈实现队列

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

实现 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(双端队列)来模拟一个栈,只要是标准的栈操作即可。

232.用栈实现队列版本2

代码

在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。

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() {
        dumpstackIn();
        return stackOut.pop();//删除此堆栈顶部的对象,并将该对象作为此函数的值返回。
    }

    public int peek() {
        dumpstackIn();
        return stackOut.peek();//查看此堆栈顶部的对象,而不将其从堆栈中删除。
    }

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

    private void dumpstackIn() {//注意:pop peek都需要用到这个操作,所以写成单独的函数
        if (!stackOut.isEmpty())
            return;
        while (!stackIn.isEmpty()) {
            stackOut.push(stackIn.pop());
        }
    }
}


/**
 * 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)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

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

注意:

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

代码

一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。

  • 可以只用一个队列模拟栈,但是要两个栈才能模拟一个队列
class MyStack {
    Queue<Integer> q;
    public MyStack() {
        q = new LinkedList<Integer>();
    }
    //这里的reposition移动处理可以放在pop和top中,但是放在push里面更加方便一些
    //放在pop和top中可能会出问题,注意处理好
    public void push(int x) {
        q.add(x);
        int size = q.size();
        while (size !=1) {
            q.offer(q.poll());
            size--;
        }
    }

    public int pop() {
        return q.poll();
    }

    public int top() {
        return q.peek();
    }

    public boolean empty() {
        return q.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. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

**输入:**s = “()”

**输出:**true

示例 2:

**输入:**s = “()[]{}”

**输出:**true

示例 3:

**输入:**s = “(]”

**输出:**false

示例 4:

**输入:**s = “([])”

**输出:**true

代码:栈

该入栈 “(” 的时候直接入栈 “)”,方便后序的比较

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        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('}');
            } else if (stack.isEmpty() || stack.peek()!=s.charAt(i)) {
                return false;
            }else{
                stack.pop();
            }
        }
        return stack.isEmpty();
    }
}

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

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

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

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

示例:

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

代码:栈存放遍历过的元素

在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。

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

class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<Character>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (stack.empty() || stack.peek() != c) {
                stack.push(c);
            }else{
                stack.pop();
            }
        }
        StringBuffer sb = new StringBuffer();
        while (!stack.empty()) {
            sb.append(stack.pop());
        }
        sb.reverse();//注意栈的输出是倒序
        return sb.toString();
    }
}

拿字符串直接作为栈,省去了栈还要转为字符串的操作。

class Solution {
    public String removeDuplicates(String s) {
        StringBuilder res = new StringBuilder();
        int top=-1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if(top==-1 || res.charAt(top) !=c){
                res.append(c);
                top++;
            }else{
                res.deleteCharAt(top);
                top--;
            }
        }
        return res.toString();
    }
}

150. 逆波兰表达式求值 后缀转中缀(不含括号)

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例 1:

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

代码:栈

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

  • 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

    150.逆波兰表达式求值

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<Integer>();
        for (String i : tokens) {
            if (i.equals("+")) {
                stack.push(stack.pop()+stack.pop());
            } else if (i.equals("-")) {//注意操作顺序  从栈里后出来的数在运算符前
                stack.push(-stack.pop()+stack.pop());
            } else if (i.equals("*")) {
                stack.push(stack.pop()*stack.pop());
            }else if (i.equals("/")) {
                int tmp1 = stack.pop();
                int tmp2 = stack.pop();
                stack.push(tmp2/tmp1);//注意操作顺序  从栈里后出来的数在运算符前
            }else{
                stack.push(Integer.parseInt(i));
            }
        }
        return stack.pop();
    }
}

队列

  • queue
  • booleanadd(E e)如果可以在不违反容量限制的情况下立即执行此操作,则将指定的元素插入此队列,成功时返回 true ,如果当前没有空间,则抛出 IllegalStateException
    Eelement()检索但不删除此队列的头部。
    booleanoffer(E e)如果可以在不违反容量限制的情况下立即执行此操作,则将指定的元素插入此队列。
    Epeek()检索但不删除此队列的头部,如果此队列为空,则返回 null
    Epoll()检索并删除此队列的头部,如果此队列为空,则返回 null
    Eremove()检索并删除此队列的头部。

优先队列和单调队列

优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的

  • 队列里的元素一定是要排序的,而且要最大值放在出队口
  • 但如果把窗口里的元素都放进队列里,窗口移动的时候,队列需要弹出元素
  • 已经排序之后的队列 怎么能把窗口要移除的元素(这个元素可不一定是最大值)弹出呢?
  • ------->其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
  • 239.滑动窗口最大值
  • 对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。

239. 滑动窗口最大值

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

暴力解

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if (n == 0) {
            return nums;
        }
        int result[] = new int[n - k + 1];
        for (int i = 0; i < result.length; i++) {
            int max = Integer.MIN_VALUE;
            for (int j = 0; j < k; j++) {
                max = Math.max(max, nums[i + j]);
            }
            result[i] = max;
        }
        return result;
    }
}

单调队列

//利用双端队列手动实现单调队列
/**
 * 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
 * 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
 */
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int n = nums.length;
        int[] res = new int[n - k + 1];
        int idx = 0;
        for(int i = 0; i < n; i++) {
            // 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点
            // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
            while(!deque.isEmpty() && deque.peek() < i - k + 1){
                deque.poll();
            }
            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            deque.offer(i);

            // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
            if(i >= k - 1){
                res[idx++] = nums[deque.peek()];
            }
        }
        return res;
    }
}

347. 前 K 个高频元素(模板记忆)

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

示例 1:

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

示例 2:

输入: nums = [1], k = 1
输出: [1]

代码:哈希统计出现次数,优先队列排序

  • https://leetcode.cn/problems/top-k-frequent-elements/solutions/3049202/347-qian-k-ge-gao-pin-yuan-su-by-timeliu-zdg3/

  • map.merge(num, 1, Integer::sum);map.put(num, map.getOrDefault(num, 0) + 1); 的比较:https://www.cnblogs.com/xinyangblog/p/18677672

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //按照出现次数排序
        PriorityQueue<int[]> q=new PriorityQueue<>((a,b)->{
            if(a[1]!=b[1])return b[1]-a[1];
            else return a[0]-b[0];//这里不加也行 直接返回return b[1]-a[1];,字母好像自动按照字典序了
        });//自定义  ,后-前  从大到小排序 大根堆  队列最前面是最大的数
        //使用map记录每个数字的出现次数
        Map<Integer,Integer> map=new HashMap<>();
        for (int num : nums) {
            map.put(num,map.getOrDefault(num,0)+1);
        }
        //将数字和出现次数放入优先队列中
        for (Integer key : map.keySet()) {
            q.add(new int[]{key,map.get(key)});//int a[]{},{}的作用:创建数组的同时直接为数组元素赋值
        }
        int[] res=new int[k];
        //依次推出堆顶元素即可
        for (int i = 0; i < k; i++) {
            res[i]=q.poll()[0];
        }
        return res; 
    }
}

注意:

  • map.put(num,map.getOrDefault(num,0)+1);

  • PriorityQueue<int[]> q=new PriorityQueue<>((a,b)->{
    if(a[1]!=b[1])return b[1]-a[1];
    else return a[0]-b[0];
    });

  • q.add(new int[]{key,map.get(key)});

### LeetCode Java 题教程和技巧 #### 使用合适的数据结构 对于特定类型的题目,选择适当的数据结构可以简化问题并提高效率。例如,在处理队列操作时,`Deque<Integer> deque = new LinkedList<>();` 是一种有效的方式来初始化双端队列[^1]。 #### 动态规划的应用实例 以格雷编码为例,该类问题可以通过动态规划的方法解决。具体来说,基于前一阶段的结果构建下一阶段的解决方案。比如从第 `n` 层的格雷码序列出发,通过在其反转后的每一个元素前面加上 '1' 来形成新的 `(n+1)` 层格雷码列表[^3]。 #### 子问题分解策略 当面对复杂度较高的算法设计时,将大问题拆分成若干个小而简单的子问题是常见的做法之一。如果能够确保这些子问题之间相互独立,则整个过程中的总工作量大致等于各部分之和。这表明随着输入大小的增长,所需处理的任务数目呈线性增长趋势,即时间复杂度为 O(n)[^4]。 #### 实战练习建议 为了更好地掌握编程技能以及应对实际面试场景下的挑战,定期参与像力扣这样的在线平台上的竞赛活动是非常有益处的。这里不仅提供了丰富的习题资源供学习者反复操练,还允许用户提交自己的解答并与他人分享交流心得体验[^2]。 ```java // 示例代码:创建一个双向链表作为双端队列 Deque<Integer> deque = new LinkedList<>(); deque.addFirst(1); // 向头部添加元素 deque.addLast(2); // 向尾部添加元素 System.out.println(deque.pollFirst()); // 移除并返回最左边的元素 System.out.println(deque.pollLast()); // 移除并返回最右边的元素 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

~柠月如风~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值