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:

  1. Any left parenthesis '(' must have a corresponding right parenthesis ')'.
  2. Any right parenthesis ')' must have a corresponding left parenthesis '('.
  3. Left parenthesis '(' must go before the corresponding right parenthesis ')'.
  4. '*' could be treated as a single right parenthesis ')' or a single left parenthesis '(' or an empty string.
  5. An empty string is also valid.

Example 1:

Input: "()"
Output: True

Example 2:

Input: "(*)"
Output: True

Example 3:

Input: "(*))"
Output: True

Note:

  1. The string size will be in the range [1, 100].

这道题让我们验证括号字符串,跟之前那道Valid Parentheses有些类似。不同之处在于这道题不只有小括号,还存在星号,星号可以当左括号,右括号,或空来使用,问我们能不能得到一个合法的括号字符串。那么我们想,如果不存在星号,那么这题是不是异常的简单,我们甚至连stack都可以不用,直接用一个变量,遇到左括号,自增1,遇到右括号,如果此时计数器已经为0了,直接返回false,否则自减1,一旦计数器出现了负数,立即返回false,最后还要看变量是否为0即可。但是由于星号的存在,这道题就变的复杂了,由于星号可以当括号用,所以当遇到右括号时,就算此时变量为0,也可以用星号来当左括号使用。那么星号什么时候都能当括号来用吗,我们来看两个例子 *) 和 *( ,在第一种情况下,星号可以当左括号来用,而在第二种情况下,无论星号当左括号,右括号,还是空,*( 都是不对的。当然这种情况只限于星号和左括号之间的位置关系,而只要星号在右括号前面,就一定可以消掉右括号。那么我们使用两个stack,分别存放左括号和星号的位置,遍历字符串,当遇到星号时,压入星号栈star,当遇到左括号时,压入左括号栈left,当遇到右括号时,此时如果left和star均为空时,直接返回false;如果left不为空,则pop一个左括号来抵消当前的右括号;否则从star中取出一个星号当作左括号来抵消右括号。当循环结束后,我们希望left中没有多余的左括号,就算有,我们可以尝试着用星号来抵消,当star和left均不为空时,进行循环,如果left的栈顶左括号的位置在star的栈顶星号的右边,那么就组成了 *( 模式,直接返回false;否则就说明星号可以抵消左括号,各自pop一个元素。最终退出循环后我们看left中是否还有多余的左括号,没有就返回true,否则false,参见代码如下:

解法一:

class Solution {
public:
bool checkValidString(string s) {
stack<int> left, star;
for (int i = ; i < s.size(); ++i) {
if (s[i] == '*') star.push(i);
else if (s[i] == '(') left.push(i);
else {
if (left.empty() && star.empty()) return false;
if (!left.empty()) left.pop();
else star.pop();
}
}
while (!left.empty() && !star.empty()) {
if (left.top() > star.top()) return false;
left.pop(); star.pop();
}
return left.empty();
}
};

如果你觉得上面的解法逻辑稍稍复杂了一些,我们来看一种逻辑无比简单的解法。既然星号可以当左括号和右括号,那么我们就正反各遍历一次,正向遍历的时候,我们把星号都当成左括号,此时用经典的验证括号的方法,即遇左括号计数器加1,遇右括号则自减1,如果中间某个时刻计数器小于0了,直接返回false。如果最终计数器等于0了,我们直接返回true,因为此时我们把星号都当作了左括号,可以跟所有的右括号抵消。而此时就算计数器大于0了,我们暂时不能返回false,因为有可能多余的左括号是星号变的,星号也可以表示空,所以有可能不多,我们还需要反向q一下,哦不,是反向遍历一下,这是我们将所有的星号当作右括号,遇右括号计数器加1,遇左括号则自减1,如果中间某个时刻计数器小于0了,直接返回false。遍历结束后直接返回true,这是为啥呢?此时计数器有两种情况,要么为0,要么大于0。为0不用说,肯定是true,为啥大于0也是true呢?因为之前正向遍历的时候,我们的左括号多了,我们之前说过了,多余的左括号可能是星号变的,也可能是本身就多的左括号。本身就多的左括号这种情况会在反向遍历时被检测出来,如果没有检测出来,说明多余的左括号一定是星号变的。而这些星号在反向遍历时又变做了右括号,最终导致了右括号有剩余,所以当这些星号都当作空的时候,左右括号都是对应的,即是合法的。你可能会有疑问,右括号本身不会多么,其实不会的,如果多的话,会在正向遍历中被检测出来,参见代码如下:

解法二:

class Solution {
public:
bool checkValidString(string s) {
int left = , right = , n = s.size();
for (int i = ; i < n; ++i) {
if (s[i] == '(' || s[i] == '*') ++left;
else --left;
if (left < ) return false;
}
if (left == ) return true;
for (int i = n - ; i >= ; --i) {
if (s[i] == ')' || s[i] == '*') ++right;
else --right;
if (right < ) return false;
}
return true;
}
};

下面这种方法是用递归来写的,思路特别的简单直接,感觉应该属于暴力破解法。使用了变量cnt来记录左括号的个数,变量start表示当前开始遍历的位置,那么在递归函数中,首先判断如果cnt小于0,直接返回false。否则进行从start开始的遍历,如果当前字符为左括号,cnt自增1;如果为右括号,若cnt此时小于等于0,返回false,否则cnt自减1;如果为星号,我们同时递归三种情况,分别是当星号为空,左括号,或右括号,只要有一种情况返回true,那么就是true了。如果循环退出后,若cnt为0,返回true,否则false,参见代码如下:

解法三:

class Solution {
public:
bool checkValidString(string s) {
return helper(s, , );
}
bool helper(string s, int start, int cnt) {
if (cnt < ) return false;
for (int i = start; i < s.size(); ++i) {
if (s[i] == '(') {
++cnt;
} else if (s[i] == ')') {
if (cnt <= ) return false;
--cnt;
} else {
return helper(s, i + , cnt) || helper(s, i + , cnt + ) || helper(s, i + , cnt - );
}
}
return cnt == ;
}
};

下面这种解法是论坛上排第一的解法,感觉思路清新脱俗,博主研究了好久,参考了网友的留言才稍稍弄懂了一些,这里维护了两个变量low和high,其中low表示在有左括号的情况下,将星号当作右括号时左括号的个数(这样做的原因是尽量不多增加右括号的个数),而high表示将星号当作左括号时左括号的个数。是不是很绕,没办法。那么当high小于0时,说明就算把星号都当作左括号了,还是不够抵消右括号,返回false。而当low大于0时,说明左括号的个数太多了,没有足够多的右括号来抵消,返回false。那么开始遍历字符串,当遇到左括号时,low和high都自增1;当遇到右括号时,只有当low大于0时,low才自减1,保证了low不会小于0,而high直接自减1;当遇到星号时,只有当low大于0时,low才自减1,保证了low不会小于0,而high直接自增1,因为high把星号当作左括号。当此时high小于0,说明右括号太多,返回false。当循环退出后,我们看low是否为0,参见代码如下:

解法四:

class Solution {
public:
bool checkValidString(string s) {
int low = , high = ;
for (char c : s) {
if (c == '(') {
++low; ++high;
} else if (c == ')') {
if (low > ) --low;
--high;
} else {
if (low > ) --low;
++high;
}
if (high < ) return false;
}
return low == ;
}
};

类似题目:

Valid Parentheses

Special Binary String

参考资料:

https://leetcode.com/problems/valid-parenthesis-string/

https://leetcode.com/problems/valid-parenthesis-string/discuss/107566/Java-12-lines-solution-backtracking

https://leetcode.com/problems/valid-parenthesis-string/discuss/139759/Java-Very-easy-solution.-No-recursion-dp.

https://leetcode.com/problems/valid-parenthesis-string/discuss/107577/Short-Java-O(n)-time-O(1)-space-one-pass

https://leetcode.com/problems/valid-parenthesis-string/discuss/107572/Java-using-2-stacks.-O(n)-space-and-time-complexity.

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Valid Parenthesis String 验证括号字符串的更多相关文章

  1. [LeetCode] 678. Valid Parenthesis String 验证括号字符串

    Given a string containing only three types of characters: '(', ')' and '*', write a function to chec ...

  2. [leetcode]678. Valid Parenthesis String验证有效括号字符串

    Given a string containing only three types of characters: '(', ')' and '*', write a function to chec ...

  3. LeetCode Valid Parenthesis String

    原题链接在这里:https://leetcode.com/problems/valid-parenthesis-string/description/ 题目: Given a string conta ...

  4. 【LeetCode】678. Valid Parenthesis String 解题报告(Python)

    [LeetCode]678. Valid Parenthesis String 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人 ...

  5. leetcode 678. Valid Parenthesis String

    678. Valid Parenthesis String Medium Given a string containing only three types of characters: '(', ...

  6. [LeetCode] Valid Palindrome II 验证回文字符串之二

    Given a non-empty string s, you may delete at most one character. Judge whether you can make it a pa ...

  7. 678. Valid Parenthesis String

    https://leetcode.com/problems/valid-parenthesis-string/description/ 这个题的难点在增加了*,*可能是(也可能是).是(的前提是:右边 ...

  8. Leetcode#344. Reverse String(反转字符串)

    题目描述 编写一个函数,其作用是将输入的字符串反转过来. 示例 1: 输入: "hello" 输出: "olleh" 示例 2: 输入: "A man ...

  9. [Swift]LeetCode678. 有效的括号字符串 | Valid Parenthesis String

    Given a string containing only three types of characters: '(', ')' and '*', write a function to chec ...

随机推荐

  1. Linux运维人员共用root帐户权限审计(转至马哥Linux运维)

    一.应用场景 在中小型企业,公司不同运维人员基本都是以root 账户进行服务器的登陆管理,缺少了账户权限审计制度.不出问题还好, 出了问题,就很难找出源头.这里介绍下,如何利用编译bash 使不同的客 ...

  2. drbd(三):drbd的状态说明

    本文目录:1.drbd配置文件2.状态 2.1 连接状态(connect state,cs)和复制状态 2.2 角色状态(roles,ro) 2.3 磁盘状态(disk state,ds) 2.4 I ...

  3. web服务器学习3---httpd 2.4.29日志处理

    .rotarelogs分割工具 如果有虚拟主机在虚拟主机配置文件中配置,否则在主配置文件中修改. 1.1修改配置文件 vi /usr/local/httpd/conf/conf.d/vhosts.co ...

  4. java调用kettle的job和transfer工具类

    package com.woaiyitiaocai.util; import java.util.Map; import java.util.UUID; import org.apache.log4j ...

  5. Could not create pool connection. The DBMS driver exception was: null, message from server: "Host '192.168.XX.XX' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'

    早上打开浏览器准备登陆某个系统,发现Error 404--Not Found,有点奇怪,这个服务器应该没人用了才对,然后到weblogic后台去看日志,报如下错误: "Could not c ...

  6. 转:运行page页面时的事件执行顺序及页面的回发与否深度了解

    using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Secu ...

  7. 20162318 实验三《 敏捷开发与XP实践》实验报告

    北京电子科技学院(BESTI) 实 验 报 告 课程:程序设计与数据结构 班级:1623班 姓名:张泰毓 指导老师:娄老师.王老师 实验日期:2017年5月12日 实验密级:非密级 实验器材:带Lin ...

  8. 结对编程作业——四则运算GUI程序

    毛忠庆 201421122088 赵嘉楠 201421122065 源代码存放位置:https://gitee.com/ouwen0819/SiZeYunSuan.git 题目描述 使用 -n 参数控 ...

  9. Twisted UDP编程技术

    实战演练1:普通UDP UDP是一种无连接对等通信协议,没有服务器和客户端概念,通信的任何一方均可通过通信原语直接和其他方通信 1.相对于TCP,UDP编程只需定义DatagramProtocol子类 ...

  10. 使用Spark MLlib进行情感分析

    使用Spark MLlib进行情感分析             使用Spark MLlib进行情感分析 一.实验说明 在当今这个互联网时代,人们对于各种事情的舆论观点都散布在各种社交网络平台或新闻提要 ...