【LeetCode 30天挑战活动】Day 16. Valid Parenthesis String

这篇博客讨论了LeetCode的第16天挑战,涉及有效括号字符串的验证。解析了题目要求,指出了解题关键是理解'*'字符的三种匹配方式。博主分享了两种解题思路:一是使用栈来处理,但需要考虑'*'与括号的相对位置;二是通过贪心算法,利用'*'形成可能的值区间,确保最后区间的最小值为0,表示匹配成功。

阴间作业和阴间小学期害人不浅: (

题目描述

(我太懒了就直接复制原题了)

Given a string containing only three types of characters: ‘(’, ‘)’ and ‘*’, write a function to check whether this string is valid. We define the validity of a string by these rules:
Any left parenthesis ‘(’ must have a corresponding right parenthesis ‘)’.
Any right parenthesis ‘)’ must have a corresponding left parenthesis ‘(’.
Left parenthesis ‘(’ must go before the corresponding right parenthesis ‘)’.
‘*’ could be treated as a single right parenthesis ‘)’ or a single left parenthesis ‘(’ or an empty string.
An empty string is also valid.

题目中主要是要理解*可以有三种匹配方式
(1)作为左括号匹配
(2)作为右括号匹配
(3)忽略不计(实际左括号和右括号可以完美匹配的时候就不需要*了)

解题思路

法一: 一看到括号匹配,第一反应是用栈来完成。而这题当中多了一个*的处理,由于无法在遇到*的时候是无法确定它到底是哪种匹配方式,于是我就想用一个额外的数据结构将*存下来,在遇到右括号去进行匹配的时候,和最后还剩左括号需要右括号来匹配的时候,就refer to这个*看能不能匹配上。
在第一次写的时候,我用了一个int变量来存放*的数量,当发现实际左右括号匹配不上的时候就看是否有*来做一个通配。但很快发现这个方法是有问题的。
"**("为例,遍历下来会得到*的数量为2,最后发现左括号的时候refer to*的数量发现有*可以匹配,那么结果就为true,但很显然这个字符串是不符合要求的。

在上面的方法中,我忽略了一个关键问题,就是*(的相对位置,很显然,在(之前出现的*是无法作为)来匹配的。因此,我在保存下无法确定的*时,不能只记录下数量,而是要将相对位置也记录下来。于是改进思路,用两个栈来存放,一个作为主栈,存放(*并保持相对位置,另一个作为辅助栈,在遇到)要先去主栈中找(时临时存放*。大致步骤如下(栈1为主栈,栈2为辅助栈):
遍历字符串的每个字符,
(1)遇到(*,入栈1
(2)遇到),从栈顶开始在主栈中找(,如果遇到的是*,就将其入栈2,直至找到(;如果最后栈1空,就用*来匹配
(3)匹配完)后将栈2中的*还原至栈1中
最后判断是否匹配成功:
在栈1中找(,并将一路上遇到的*入栈2,如果遇到的(都能有*匹配,且最后栈1空,则匹配成功,否则失败
这个方法时间复杂度O(N²),空间复杂度O(N)
代码如下:

class Solution {
    public boolean checkValidString(String s) {
        if (s == null || s.length() == 0) {
            return true;
        }
        
        Stack<Character> mainStack = new Stack<>();
        Stack<Character> starStack = new Stack<>();
        
        for (int i = 0; i < s.length(); ++i) {
            char tmp = s.charAt(i);
            
            if (tmp != ')') {
                mainStack.push(tmp);
            }
            else {
                char top = '\0';
                
                while (!mainStack.empty() && (top = mainStack.peek()) == '*') {
                    mainStack.pop();
                    starStack.push(top);
                }
                
                if (!mainStack.empty()) {
                    mainStack.pop();
                }
                else if (!starStack.empty()) {
                    starStack.pop();
                }
                else {
                    return false;
                }
                
                while (!starStack.empty()) {
                    mainStack.push(starStack.peek());
                    starStack.pop();
                }
            }
        }
        
        while (!mainStack.empty()) {
            if (mainStack.peek() == '*') {
                mainStack.pop();
                starStack.push('*');
            }
            else {
                // '('
                if (!starStack.empty()) {
                    starStack.pop();
                    mainStack.pop();
                }
                else {
                    return false;
                }
            }
        }
        
        return true;
    }
}

法二: 题解中提供了一种贪心算法(虽然我是没看出来贪心在哪里),比较巧妙。不考虑*的情况的话,其实我们只需要遍历时记录下(的数量,然后在遇到)时根据数量去判断是否可以匹配,在这个思路中,(的数量在一个时刻上都是确定值。但现在有了*,遍历遇到*时,(的数量就无法确定了,它可以作为一个(用来匹配,那么数量就要+1,可以作为一个)来匹配,那么数量应该-1,也可以直接忽略,那么数量不变。事实上,把*考虑进来之后,(的值就不再是一个确定值,而是一个可能区间
那么,我们就可以真的用一个区间来表示(的值的可能取值,只要最后它的最小值为0,那么就说明可以成功匹配。具体步骤如下:
初始时lowhigh均为0,遍历字符串的每一个字符,
(1)遇到(low++ && high++
(2)遇到*low-- && high++(这一步我觉得是理解区间在这题里的意义的重点)
(3)遇到)low-- && high--,(因为)必定会消耗一个(或者*
代码如下:

class Solution {
    public boolean checkValidString(String s) {
        if (s == null || s.length() == 0) {
            return true;
        }
        
        int low = 0, high = low;
        
        for (int i = 0; i < s.length(); ++i) {
            char tmp = s.charAt(i);
            
            low += (tmp == '(')? 1 : -1;
            high += (tmp != ')')? 1 : -1;
            
            if (high < 0)
                break;
            
            low = Math.max(0, low);
        }
        
        return low == 0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值