JAVA字符串基础——代码随想录 栈与队列

栈和队列

Leetcode 232. 用栈实现队列

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

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾

  • int pop() 从队列的开头移除并返回元素

  • int peek() 返回队列开头的元素

  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

  • 思路:用两个栈来模拟队列,入栈和入队的操作是一样的。出队时,因为要出的元素是先入栈的元素(在栈底),所以用一个栈翻转过来。

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() {
        if (!stackOut.isEmpty()){
            return stackOut.pop();
        }
        else{
            while(!stackIn.isEmpty()){
                stackOut.push(stackIn.pop());
            }
            return stackOut.pop();
            }   
    }
    
    public int peek() {
        if (stackOut.isEmpty()){
            while(!stackIn.isEmpty()){
                stackOut.push(stackIn.pop());
            }
        }
        if(empty()) return -1;
        else{
            return stackOut.peek();
        }
        
    }
    
    public boolean empty() {
        if (stackOut.isEmpty() && stackIn.isEmpty()){
            return true;
        }
        return false;
    }
}

Leetcode 225. 用队列实现栈

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

实现 MyStack 类:

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

    Queue<Integer> que1;

    public MyStack() {
        que1 = new LinkedList<>();
    }
    
    public void push(int x) {
        que1.offer(x);
        int size = que1.size();
         while(size-- > 1){
            que1.offer(que1.poll());
        }
    }
    
    public int pop() {
        
        return que1.poll();
    }
    
    public int top() {
       return que1.peek();
    }
    
    public boolean empty() {
        return que1.isEmpty();
    }
}

Leetcode 20. 有效的括号

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。
  • 一开始的思路:
    • 想到用栈来解决了,遇到左括号进栈,右括号出栈,但是怎么判断对应左右括号呢?我没有想到
  • 思路:遇到'('入栈')',遇到'{'入栈'}',遇到'[',入栈']'),因为只能(){}[]这样匹配,({)}这种情况不是有效的括号哦。所以判断新字符时:
    • 入栈
    • 和栈顶相同,栈顶出栈
    • 和栈顶不同,则括号不匹配
    • 到最后看栈是否为空,不空则不匹配
class Solution {
    public boolean isValid(String s) {
        char[] chars = s.toCharArray();
        Stack<Character> stack= new Stack<>();
        for(int i = 0; i < chars.length; i++){
            if(chars[i] == '('){
                stack.push(')');
            }
            else if(chars[i] == '['){
                stack.push(']');
            }
            else if(chars[i] == '{'){
                stack.push('}');
            }else if (stack.isEmpty() || stack.peek() != chars[i]) {
                return false;}
            else {//如果是右括号判断是否和栈顶元素匹配
                stack.pop();
            }
        }
        return stack.isEmpty();
    }
}

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

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

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

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

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();
        char[] chars = s.toCharArray();

        for(int i = 0; i < chars.length; i++){
            if(stack.isEmpty() || stack.peek() != chars[i]){
                stack.push(chars[i]);
                }
                else{
                    stack.pop();
                }
            }
        
        String str = "";
        //剩余的元素即为不重复的元素
        while (!stack.isEmpty()) {
            str = stack.pop() + str;
        }
        return str;
    }
}

debug半天不知道哪错了,无语,也不知道为什么就对了。注意出栈后字符串的顺序是反的,需要翻转一下。

Leetcode 150. 逆波兰表达式求值

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

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

注意:

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

错误:

  1. 如何判断字符串相等?可以使用equals方法检测两个字符串是否相等。字符串1.equals(字符串2);
    返回结果为boolean;字符串相等为true,不相等为false。
  2. 一定不要使用==运算符检测两个字符串是否相等!
    这个运算符只能够确定两个字符串是否存放在同一个位置上。
  3. 如何将字符串String转换为整型Int?使用Integer.valueOf(String)方法。待转换字符串的内容必须为纯数字。 如果待转字符串中出现了除数字以外的其他字符,则程序会抛出异常。Integer.parseInt(String)方法的性能更胜一筹。
  4. 减或者除需要调换顺序,因为被减数or被除数是后出栈的。
class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (String s : tokens){
            if ("+".equals(s)){
                stack.push(stack.pop() + stack.pop()); 
            }else if ("-".equals(s)){
                //后出栈的减先出栈的
                stack.push(-stack.pop() + stack.pop()); 
            }else if ("*".equals(s)){
                stack.push(stack.pop() * stack.pop()); 
            }else if ("/".equals(s)){
                //后出栈的除先出栈的
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2 / temp1); 
            }else{
                stack.push(Integer.valueOf(s));
            }
        }
        return stack.pop();
    }
}

Leetcode 239. 滑动窗口最大值

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

返回 滑动窗口中的最大值

输入: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

一刷理解思路:

  • 滑动窗口移动的时候仿佛一个队列,队尾pop队头push。该队列是一个单调队列,队头位置存储的永远是当前滑动窗口中的最大值。
  • 入队时比较队列中的值,若是比队尾的值小,则直接入队,若是比队尾的值大,则把队尾poll出来,继续比较,直到队列为空or队尾较大, 这样保证了队首元素永远是滑动窗口中最大的元素
  • 出队有两种情况,一种是入队的值较大把前面元素卷出去了,另一种是当队首的值等于滑动窗口滑掉的值时,把该值从队列中pop出去。
class myDeque{
    Deque<Integer> deque = new LinkedList<>();
    void poll(int val) {
        //弹出元素时,要判断队列当前是否为空,当队首的值等于滑动窗口滑掉的值时,把该值从队列中pop出去
        if (!deque.isEmpty() && val == deque.peek()) {
            deque.poll();
        }
    }
    void push(int val){
        //添加元素时,首先判断和队尾的元素大小,如果队尾较大则直接进去;
        //如果队尾较小,则把队尾poll出来,继续比较,直到队列为空or队尾较大
        // 这样保证了队首元素永远是滑动窗口中最大的元素
        while (!deque.isEmpty() && val > deque.getLast()) {
            deque.removeLast();
        }
        deque.add(val);
    }

    int getMaxValue() {
        return deque.peek();
    }
}


class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int len = nums.length - k + 1;
        int max[] = new int[len];
        int num = 0;
        myDeque myque = new myDeque();
        for (int i = 0; i < k; i++) {
            myque.push(nums[i]);
        }
        max[num++] = myque.getMaxValue();
        for(int i = k; i < nums.length; i++){
            myque.poll(nums[i - k]);
            myque.push(nums[i]);
            max[num++] = myque.getMaxValue();
        }
    return max;
    }
}

Leetcode 347. 前 K 个高频元素

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

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
  • 大顶堆or小顶堆非常适合在大量数据中求前k个元素。

  • 为什么使用小顶堆?堆pop的时候是pop的堆顶元素,如果是大顶堆的话就把堆顶最大的元素pop出去了。如果是小顶堆的话每次pop最小的元素,剩下的就是大的元素。

  • 时间复杂度:nlogk。n是遍历nums数组,logk是维护小顶堆。

  • hashmap.getOrDefault(Object key, V defaultValue)
    
  • getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。key - 键,defaultValue - 当指定的key并不存在映射关系中,则返回的该默认值

  • 用优先级队列实现小顶堆

  • 入堆时如果堆中个数少于k则直接入堆,若大于k,判断入堆的二元组value与堆顶二元组的value大小,堆顶小则出堆塞进去新入堆的。

  • 最后倒序pop到数组中。

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //用hashmap对应数值和出现次数。key-value模式
        //key为数组元素值,val为对应出现次数 
        Map<Integer,Integer> map = new HashMap<>();
        for(int num: nums){
            //getOrDefault(num,0):寻找num在hashmap中对应的值,若是没有,则返回0,若是有则返回对应value
            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;
    }
}
  • pair的用法

     @Test
        public void TestPair() {
            Pair<String,String> pair = Pair.of("left","right");
            System.out.println("left = " + pair.getLeft());
            System.out.println("right = " + pair.getRight());
            System.out.println("key = " + pair.getKey());
            System.out.println("value = " + pair.getValue());
            Pair<String,String> mutablePair = new MutablePair<>("left","right");
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-otmlw34b-1677853809817)(C:\Users\wsco24\AppData\Roaming\Typora\typora-user-images\image-20230303221018528.png)]

    上述结果可以看出,pair的左为key,右为value。源码如下:

    // 这里的获取key其实就是获取getLeft()方法的值
    public final L getKey() {
            return this.getLeft();
        }
        // 这里的获取value  其实就是获取getRight()方法的值
        public R getValue() {
            return this.getRight();
        }
    
  • map.entrySet()返回映射中包含的映射的 Set 视图。

HashMap<Integer, String> sites = new HashMap<>();

        // 往 HashMap 添加一些元素
        sites.put(1, "Google");
        sites.put(2, "Runoob");
        sites.put(3, "Taobao");
        System.out.println("sites HashMap: " + sites);

        // 返回映射关系中 set view
        System.out.println("Set View: " + sites.entrySet());
输出:
sites HashMap: {1=Google, 2=Runoob, 3=Taobao}
Set View: [1=Google, 2=Runoob, 3=Taobao]

entrySet() 方法可以与 for-each 循环一起使用,用来遍历迭代 HashMap 中每一个映射项。

for(Entry<String, Integer> entry: numbers.entrySet())

HashMap<String, Integer> numbers = new HashMap<>();
        numbers.put("One", 1);
        numbers.put("Two", 2);
        numbers.put("Three", 3);
for(Entry<String, Integer> entry: numbers.entrySet()) {
            System.out.print(entry);
            System.out.print(", ");
        }
输出:
Entries: One=1, Two=2, Three=3, 
优先级队列PriorityQueue

优先级队列,依旧是 顾名思义,优先级队列会自动按照元素的权值排列,也就是说,优先级队列其实有反 先进先出规则,但是其对外接口依旧是从队头取元素,队尾添加元素,再无其他存取方式,看起来就是一个队列

优先级队列其实是一个 披着队列外皮的堆

什么是堆?

堆通常可以被看成一个完全二叉树的数组对象,树中的每个节点都不小于(或不大于)其左右孩子的值

  • 如果父节点大于左右孩子的值,那么是大顶堆
  • 如果父节点小于左右孩子的值,那么是小顶
优先级队列的特点
  • 优先级队列里的元素必须实现内部比较器或者外部比较器
  • 优先级队列拥有小顶堆的所有特性, 默认是 小顶堆
  • 优先级队列不是线程安全的
  • 不允许使用 null元素
  • 优先级队列本身是无序的,但是取出的元素所排成的序列才是有序序列
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值