《LeetCode之每日一题》:147.有效的括号字符串

Posted 是七喜呀!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《LeetCode之每日一题》:147.有效的括号字符串相关的知识,希望对你有一定的参考价值。

有效的括号字符串

有关题目

给定一个只包含三种字符的字符串:( ,) 和 *,
写一个函数来检验这个字符串是否为有效字符串。

有效字符串具有如下规则:

任何左括号 ( 必须有相应的右括号 )。
任何右括号 ) 必须有相应的左括号 ( 。
左括号 ( 必须在对应的右括号之前 )* 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
一个空字符串也被视为有效字符串。
示例 1:

输入: "()"
输出: True
示例 2:

输入: "(*)"
输出: True
示例 3:

输入: "(*))"
输出: True
注意:

字符串大小将在 [1100] 范围内。

题解

法一:动态规划
参考官方题解

思路:
①dp[i][j]含义:在s中,下标从i 到 j (0 <= i <= j < n),对应的字符串是否有效
②初始化;
	①长度为 1 字符串,为 "*", 为真。
	②长度为 2 字符串,为"()","(*","*)","**",为真。
③动态转移方程:
  长度大于 2 字符串dp[i][j](0 <= i < j < n)的真假,取决于以下两种情况中的一种即可。
    ①c1 = s[i], c2 = s[j], 满足(c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')。
    此时dp[i][j] = dp[i + 1][j - 1];[i, j] = [i, k] U [k + 1, j],即我们说明[i, k][k + 1, j]2 个有效的括号字符串。

④注意点:由于dp[i][j] = dp[i + 1][j - 1],所以我们要注意遍历的为倒序
class Solution {
public:
    bool checkValidString(string s) {
        int n = s.size();
        vector<vector<bool>> dp(n, vector<bool>(n, false));

        //长度为 1 字符串,为 "*", 为真
        for (int i = 0; i < n; ++i){
            char c = s[i];
            dp[i][i] = c == '*';
        }

        //长度为 2 字符串,为"()","(*","*)","**",为真
        for (int i = 0; i < n - 1; ++i){
            char c1 = s[i], c2 = s[i + 1];
            if ((c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')){
                dp[i][i + 1] = true;
            }
        }

    //长度大于 2 字符串dp[i][j](0 <= i < j < n)的真假
        for (int i = n - 3; i >= 0; --i){
            char c1 = s[i];
            for (int j =  + 2; j < n; ++j){
                char c2 = s[j];
                if ((c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')){
                    dp[i][j] = dp[i + 1][j - 1];
                }
                for (int k = i; k < j && !dp[i][j]; ++k){
                    dp[i][j] = dp[i][k] && dp[k + 1][j];
                }
            }
        }
        return dp[0][n - 1];
    }
};

法二:正反遍历
参考官方题解评论区Amber_Han

思路:
保证左右括号的有效性,'*'作为万能括号可用可不用
①从前往后确定有效的 ')'
②从后往前确定有效的 '('
class Solution {
public:
    bool checkValidString(string s) {
        int n = s.size();
        int l = 0, m = 0;

        //正向遍历,判断左括号数量是否足够,且是否在满足有效括号
        for (int i = 0; i < n; ++i){
            char c = s[i];
            if (c == '('){
                ++l;
            } else if (c == '*'){
                ++m;
            } else {
                --l;
            }
            if (l < 0) --m, ++l;
            if (m < 0) return false;
        }

        //反向遍历,判断右括号数量是否足够,且是否满足有效的括号
        int r = 0;
        m = 0;
        for (int i = n - 1; i >= 0; --i){
            char c = s[i];
            if (c == '('){
                --r;
            } else if (c == '*'){
                ++m;
            } else {
                ++r;
            }
            if (r < 0) --m, ++r;
            if (m < 0) return false;
        }
        return true;
    }
};

时间复杂度:O(n),正反遍历时间复杂度O(n),总时间复杂度O(n)
空间复杂度:O(1)

法三:栈

思路:
根据栈先进后出特点,本题可以使用栈。

我们使用两个栈分别存储左括号 与 '*',具体地:
	①碰到左括号与 '*' 我们入栈。
	②碰到右括号我们进行以下判断:
	 由于'*' 可看作空白字符串,我们优先存在的除去左括号,
	 若不存在左括号,我们使用'*'充当左括号,
	 若两者皆不存在,则返回false
	③由于遍历结束,还可能剩余左括号与'*',所以我们进行循环
	 我们两个栈中存放的为下标,此时'*'看作右括号时,要满足,
	 对应栈顶下标大于左括号栈顶下标,如不存在,则返回false,反之,我们拿掉栈顶元素,继续循环
	 循环结束条件为,左括号栈与星号栈有一个先为空
	 最后判断,左括号栈是否为空,为空则返回true;

class Solution {
public:
    bool checkValidString(string s) {
        int n = s.size();
        stack<char> l, a;

        for (int i = 0; i < n; ++i){
            char c = s[i];
            if (c == '('){
                l.push(i);
            } else if (c == '*'){
                a.push(i);
            } else {
                if (!l.empty()){
                //由于'*'可做空字符串,所以我们优先除去存在的左括号,若没有左括号,若有'*',我
                    l.pop();
                } else if (!a.empty()){
                    a.pop();
                } else {
                    return false;
                }
            }
        }
		
		//')' 遍历除去之后,可能还有'('与 '*' 
        while(!l.empty() && !a.empty()){
            int leftIndex = l.top();
            l.pop();
            int aIndex = a.top();
            a.pop();
            if (leftIndex > aIndex){
                return false;
            }
        }
        return l.empty();
    }
};


法四:贪心
参考官方题解


class Solution {
public:
    bool checkValidString(string s) {
        int n = s.size();
        int maxCnt = 0, minCnt = 0;
        for (int i = 0; i < n; ++i){
            char c = s[i];
            if (c == '('){
                ++maxCnt;
                ++minCnt;
            } else if (c == ')'){
                --maxCnt;
                minCnt = max(minCnt - 1, 0);
                if (maxCnt < 0){
                    return false;
                }
            } else {
                ++maxCnt;
                minCnt = max(minCnt - 1, 0);
            }
        }
        return minCnt == 0;
    }
};

时间复杂度:O(n),n为字符串 s 长度,自左往右遍历一次
空间复杂度:O(1)

以上是关于《LeetCode之每日一题》:147.有效的括号字符串的主要内容,如果未能解决你的问题,请参考以下文章

《LeetCode之每日一题》:190.括号生成

LeetCode每日一题

《LeetCode之每日一题》:259.括号的最大嵌套深度

LeetCode每日一题2020.7.04 32. 最长有效括号

leetcode 每日一题 20. 有效的括号

《LeetCode之每日一题》:115.字符串解码