【力扣一刷】代码随想录day11(20、1047、 150)

文章讲述了如何使用栈、映射和ArrayDeque等数据结构解决有效括号匹配问题,以及在字符串处理中删除相邻重复项和逆波兰表达式求值的方法,包括多种实现策略和时间空间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

【20. 有效的括号】

方法一  利用栈(Stack类)和映射(Map接口),栈中只存左括号

方法二  只利用栈(Stack类),栈只存与左括号对应的右括号

方法三  只利用栈(Deque接口),栈只存与左括号对应的右括号

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

方法一  使用ArrayDeque实现栈,用StringBuffer拼接栈内字符串并倒序

方法二  直接使用StringBuffer充当栈,不需要倒序

方法三  拓展  双指针(比较难理解)

【150. 逆波兰表达式求值】


【20. 有效的括号】

方法一  利用栈(Stack类)和映射(Map接口),栈中只存左括号

思路:

1、构建左括号为key,右括号为value的map,用于判断是否匹配

2、构建栈,遍历输入的字符串s

如果遇到的是左边的括号,就压入栈

如果遇到的是右边的括号,就判断是否能匹配

  • 如果栈内为空,即没有可匹配的左括号,则直接返回false
  • 如果栈内不为空

        如果右边的括号匹配栈顶的左括号,弹出被匹配的左括号

        如果右边的括号不匹配栈顶的左括号,直接返回false

4、如果遍历完之后,栈内还剩左括号,证明没匹配完,返回false

class Solution {
    public boolean isValid(String s) {
        // 如果输入的括号数是奇数,则直接返回false
        if (s.length() % 2 == 1) return false;

        // 1、构建左括号为key,右括号为value的map,用于判断是否匹配
        Map<Character, Character> map = new HashMap<>();
        map.put('{', '}');
        map.put('[', ']');
        map.put('(', ')');

        Stack<Character> stack = new Stack<>();

        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            // 2、如果遇到的是左边的括号,就压入栈
            if (map.containsKey(c)){
                stack.push(c);
            }
            // 3、如果遇到的是右边的括号,就判断是否能匹配
            else{
                // -如果栈内没有左括号,即没有可匹配的左括号,则直接返回false
                if (stack.size() == 0) return false;

                // -如果栈内有左括号
                char top = stack.peek();  // 获取处于栈顶的左括号
                // --如果右边的括号匹配栈顶的左括号,弹出被匹配的左括号
                if (map.get(top).equals(c)){
                    stack.pop();
                }
                // --如果右边的括号不匹配栈顶的左括号,直接返回false
                else{
                    return false;
                }
            }
        }

        // 遍历完之后,如果栈内为空,则返回true,如果栈内还剩左括号,证明没匹配完,返回false
        return stack.isEmpty();

    }
}

时间复杂度: O(n),要遍历字符串为O(n),哈希表和栈的操作都为O(1)

空间复杂度: O(n),map固定长度3为O(1),stack最大为O(n)

方法二  只利用栈(Stack类),栈只存与左括号对应的右括号

思路:

1、构建栈,遍历输入的字符串s

如果是左括号,就把其对应的右括号压入栈中

如果都不是左括号,那剩下的就是右括号,需要考虑匹配问题

- 如果不匹配,则返回false

- 如果匹配,则将栈顶与其相等的右括号弹出

2、如果遍历完之后,栈内还有右括号,证明没匹配完,返回false

class Solution {
    public boolean isValid(String s) {
        // 如果s的长度是奇数,不可能完全匹配,则直接返回false
        if (s.length() % 2 == 1) return false;

        Stack<Character> stack = new Stack<>();

        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            // 如果是左括号,就把对应的右括号压入栈中
            if (c == '{'){
                stack.push('}');
            }
            else if (c == '['){
                stack.push(']');
            }
            else if (c == '('){
                stack.push(')');
            }
            // 如果都不是左括号,那剩下的就是右括号,需要考虑匹配问题
            // - 如果不匹配
            else if (stack.size() == 0 ||  c != stack.peek()){
                return false;
            }
            // - 如果匹配
            else{
                stack.pop();
            }
        }
        return stack.isEmpty();
    }
}

时间复杂度: O(n),要遍历字符串为O(n),栈的操作都为O(1)

空间复杂度: O(n),stack最大为O(n)

方法三  只利用栈(Deque接口),栈只存与左括号对应的右括号

【Deque接口的使用】

1、Deque接口的介绍:

  • Java中的Deque接口继承自Queue接口,Deque接口有多个实现类,其中最常用的实现类是ArrayDequeLinkedList。
  • ArrayDeque是基于数组实现的双端队列,而LinkedList是基于链表实现的双端队列

        Deque<> deque = new ArrayDeque<>();  // 基于数组实现

        Deque<> deque = new LinkedList<>();  // 基于链表实现

2、Deque接口定义的常用方法:

  • 针对<栈>的方法:push()和pop()
  • 针对<队列>的方法:offer()和poll()

因此,既可以用Deque实现栈,也可以用Deque实现队列。

注意,Queue接口只有针对队列的方法,如offer()和poll()。

class Solution {
    public boolean isValid(String s) {
        // 如果s的长度是奇数,不可能完全匹配,则直接返回false
        if (s.length() % 2 == 1) return false;

        Deque<Character> stack = new LinkedList<>();

        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            // 如果是左括号,就把对应的右括号压入栈中
            if (c == '{'){
                stack.push('}');
            }
            else if (c == '['){
                stack.push(']');
            }
            else if (c == '('){
                stack.push(')');
            }
            // 如果都不是左括号,那剩下的就是右括号,需要考虑匹配问题
            // - 如果不匹配
            else if (stack.size() == 0 ||  c != stack.peek()){
                return false;
            }
            // - 如果匹配
            else{
                stack.pop();
            }
        }
        return stack.isEmpty();
    }
}

时间复杂度: O(n),要遍历字符串为O(n),栈的操作都为O(1)

空间复杂度: O(n),stack最大为O(n)

注意:与方法二的区别只在于创建栈的时候,使用的实现类不同。

  • 方法二:Stack<Character> stack = new Stack<>();  // 基于Stack类实现
  • 方法三:Deque<Character> stack = new LinkedList<>(); // 基于Deque接口和LinkedList实现类实现

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

方法一  使用ArrayDeque实现栈,用StringBuffer拼接栈内字符串并倒序

class Solution {
    public String removeDuplicates(String s) {
        Deque<Character> stack = new ArrayDeque<>();
        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            if (stack.size() != 0 && c == stack.peek()){
                stack.pop();
            }
            else{
                stack.push(c);
            }
        }

        StringBuffer sb = new StringBuffer();
        while (!stack.isEmpty()){
            sb.append(stack.pop());
        }
        sb.reverse();
        return sb.toString();
    }
}

时间复杂度: O(n)

空间复杂度: O(n)

【注意:当在两端快速插入和删除元素、高效访问时,ArrayDeque比LinkedList更有优势】

 ArrayDeque 具有以下特点:

  1. 快速随机访问:由于 ArrayDeque 基于数组实现,可以通过索引直接访问数组中的元素,因此随机访问的性能非常高,时间复杂度为 O(1)。

  2. 高效的头部和尾部操作:ArrayDeque 可以在数组的头部和尾部进行高效的插入和删除操作,时间复杂度也为 O(1)。这是因为 ArrayDeque 在内部维护了两个指针,分别指向数组的头部和尾部,通过这两个指针可以快速插入和删除元素。当需要在头部或尾部插入或删除元素时,只需移动对应的指针即可。

而 LinkedList 虽然插入和删除的时间复杂度也为 O(1) ,但其访问的时间复杂度为 O(n) 。

方法二  直接使用StringBuffer充当栈,不需要倒序

class Solution {
    public String removeDuplicates(String s) {
        StringBuffer stack = new StringBuffer();
        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            if (stack.length() != 0 && stack.charAt(stack.length() - 1) == c){
                stack.deleteCharAt(stack.length() - 1);
            }
            else{
                stack.append(c);
            }
        }

        return stack.toString();
    }
}

时间复杂度: O(n),如果stack不需要扩容,append的时间复杂度为O(1)的情况下

空间复杂度: O(n)

注意:StringBuffer类删除指定索引字符的方法是deleteCharAt(int idx),如果是delete(int start, int end)方法,必须传入需要删除字符串的开始索引和结束索引,规则是左闭右开。

【总结:针对不同的数据类型,判断内容是否相等的方法】

  • 基本数据类型之间的比较:== 运算符
  • 引用数据类型之间的比较:equals() 方法
  • 包装数据类型之间的比较:equals() 方法
  • 基本数据类型与包装数据类型之间的比较:使用 == 时,会自动先将包装类型自动拆箱为基本类型,然后再进行内容比较。包装数据类型的变量使用 equals() 方法,也可以进行内容比较。

方法三  拓展  双指针(比较难理解)

思路:快慢指针配合用字符数组记录结果,排除相邻的两字母

  • fast指针负责遍历和读值
  • slow指针负责排除相邻的两字母,查找可以写值的位置,以及进行写值

class Solution {
    public String removeDuplicates(String s) {
        int slow = 0;
        int fast = 0;
        char[] ch = s.toCharArray();
        while (fast < s.length()){
            // 将fast读到的值写在slow指向的位置上
            ch[slow] = ch[fast];

            // 利用slow辨别匹配项,并重新选择可以写值的位置
            if (slow > 0 && ch[slow] == ch[slow - 1]){
                slow--;  // 如果出现两字母相邻且相同,则slow回退一步
            }
            else{
                slow++; // 如果未出现两字母相邻且相同,则slow继续遍历
            }

            // fast继续遍历
            fast++;
        }
        return new String(ch, 0, slow); // 不包含ch[slow]
    }
}

时间复杂度: O(n),双指针遍历整个字符数组

空间复杂度: O(n),使用额外的字符数组,复杂度为O(n)

【150. 逆波兰表达式求值】

思路:

1、创建栈

2、遍历字符串数组

  •  如果是运算符,则弹出栈顶的前两个元素按运算符运算
  • 如果不是运算符,则将字符串转成整型,再压入栈

3、最后剩下的元素就是计算结果

class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < tokens.length; i++){
            String s = tokens[i];
            // 如果是运算符,则弹出栈顶的前两个元素按运算符运算
            if (s.equals("+")){
                stack.push(stack.pop() + stack.pop());
            }
            else if (s.equals("-")){
                stack.push(- (stack.pop() - stack.pop()));
            }
            else if (s.equals("*")){
                stack.push(stack.pop() * stack.pop());
            }
            else if (s.equals("/")){
                int x = stack.pop();
                stack.push(stack.pop() / x);
            }
            // 如果不是运算符,则将字符串转成整型,再压入栈
            else{
                stack.push(Integer.valueOf(s));
            }
        }
        return stack.pop(); // 最后剩下的元素就是计算结果
    }
}

时间复杂度: O(n)

空间复杂度: O(n)

总结:

1、字符串与包装类之间的转换

  • 字符串 -> 包装类:Integer x = Integer.valueOf ( String s );
  • 包装类 -> 字符串:String s = Integer.toString ( Integer x );

2、自动装箱与自动拆箱

自动装箱:基本类型自动转为包装类型,如:Integer x = 1;

自动拆箱:包装类型自动转为基本类型,如:int y = x;

(代码最后return的是Integer类型的值,而方法规定的返回值是int型,这里隐含了自动拆箱。)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值