题目
给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:
- 任何左括号 ( 必须有相应的右括号 )。
- 任何右括号 ) 必须有相应的左括号 ( 。
- 左括号 ( 必须在对应的右括号之前 )。
- 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
- 一个空字符串也被视为有效字符串。
示例
示例1
输入: “()”
输出: True
示例2
输入: “(*)”
输出: True
示例3
输入: “(*))”
输出: True
提示
- 字符串大小将在 [1,100] 范围内。
题解
思路一
栈
如果字符串中没有星号,则只需要一个栈存储左括号,在从左到右遍历字符串的过程中检查括号是否匹配。
在有星号的情况下,需要两个栈分别存储左括号和星号。从左到右遍历字符串,进行如下操作。
-
如果遇到左括号,则将当前下标存入左括号栈。
-
如果遇到星号,则将当前下标存入星号栈。
-
如果遇到右括号,则需要有一个左括号或星号和右括号匹配,由于星号也可以看成右括号或者空字符串,因此当前的右括号应优先和左括号匹配,没有左括号时和星号匹配:
-
如果左括号栈不为空,则从左括号栈弹出栈顶元素;
-
如果左括号栈为空且星号栈不为空,则从星号栈弹出栈顶元素;
-
如果左括号栈和星号栈都为空,则没有字符可以和当前的右括号匹配,返回 \text{false}false。
遍历结束之后,左括号栈和星号栈可能还有元素。为了将每个左括号匹配,需要将星号看成右括号,且每个左括号必须出现在其匹配的星号之前。当两个栈都不为空时,每次从左括号栈和星号栈分别弹出栈顶元素,对应左括号下标和星号下标,判断是否可以匹配,匹配的条件是左括号下标小于星号下标,如果左括号下标大于星号下标则返回 false。
最终判断左括号栈是否为空。如果左括号栈为空,则左括号全部匹配完毕,剩下的星号都可以看成空字符串,此时 ss 是有效的括号字符串,返回 true。如果左括号栈不为空,则还有左括号无法匹配,此时 ss 不是有效的括号字符串,返回 false。
代码实现
const checkValidString = function (s) {
// 存储左括号下标的栈
const stack1 = [];
// 存储*号下标的栈
const stack2 = [];
for (let i = 0, len = s.length; i < len; i++) {
const char = s[i];
// 如果碰到的是右括号,则优先从左括号的栈中弹出一个,其次从*号的栈中弹出一个;否则表示没有与之匹配的符号,则返回true
if (char === ")") {
if (stack1.length) {
stack1.pop()
} else if (stack2.length) {
stack2.pop();
} else {
return false;
}
}
// 如果遇到左括号则把下标push进栈中(因为后面会用到下标比较)
if (char === "(") {
stack1.push(i)
}
// 如果遇到*号则把下标push进栈中
if (char === "*") {
stack2.push(i)
}
}
// 依次把下标弹出栈进行比较,因为可能有这种情况"***("
while (stack1.length || stack2.length) {
const index1 = stack1.pop() ?? -1;
const index2 = stack2.pop() ?? -1;
if (index1 > index2) {
return false;
}
}
return true;
};
思路二
贪心算法
使用贪心的思想,可以将空间复杂度降到 O(1)O(1)。
从左到右遍历字符串,遍历过程中,未匹配的左括号数量可能会出现如下变化:
-
如果遇到左括号,则未匹配的左括号数量加 1;
-
如果遇到右括号,则需要有一个左括号和右括号匹配,因此未匹配的左括号数量减 1;
-
如果遇到星号,由于星号可以看成左括号、右括号或空字符串,因此未匹配的左括号数量可能加 1、减 1 或不变。
基于上述结论,可以在遍历过程中维护未匹配的左括号数量可能的最小值和最大值,根据遍历到的字符更新最小值和最大值:
-
如果遇到左括号,则将最小值和最大值分别加 1;
-
如果遇到右括号,则将最小值和最大值分别减 1;
-
如果遇到星号,则将最小值减 1,将最大值加 1。
任何情况下,未匹配的左括号数量必须非负,因此当最大值变成负数时,说明没有左括号可以和右括号匹配,返回 false。
当最小值为 0 时,不应将最小值继续减少,以确保最小值非负。
遍历结束时,所有的左括号都应和右括号匹配,因此只有当最小值为 0 时,字符串 s 才是有效的括号字符串。
代码实现
/**
* @param {string} s
* @return {boolean}
*/
const checkValidString = function (s) {
let minCount = 0, maxCount = 0;
const len = s.length;
for (let i = 0; i < len; i++) {
const c = s[i];
// 如果遇到左括号最小值和最大值分别+1
if (c === '(') {
minCount++;
maxCount++;
} else if (c === ')') {
// 如果遇到右括号最小值和最大值分别-1
minCount = Math.max(minCount - 1, 0);
maxCount--;
// 如果最大值小于0,说明没有左括号或者*号与右括号匹配,则返回false
if (maxCount < 0) {
return false;
}
} else {
// 如果遇到*号,最小值-1,最大值+1
minCount = Math.max(minCount - 1, 0);
maxCount++;
}
}
return minCount === 0;
};