题目
标题和出处
标题:检查替换后的词是否有效
难度
5 级
题目描述
要求
给你一个字符串 s \texttt{s} s,请你判断它是否有效。
字符串 s \texttt{s} s 有效需要满足:假设开始有一个空字符串 t = "" \texttt{t = ""} t = "",你可以执行任意次下述操作将 t \texttt{t} t 转换为 s \texttt{s} s:
- 将字符串 "abc" \texttt{"abc"} "abc" 插入到 t \texttt{t} t 中的任意位置。形式上, t \texttt{t} t 变为 t left + "abc" + t right \texttt{t}_\texttt{left}\texttt{ + "abc" + t}_\texttt{right} tleft + "abc" + tright,其中 t = t left + t right \texttt{t} = \texttt{t}_\texttt{left}\texttt{ + t}_\texttt{right} t=tleft + tright。注意, t left \texttt{t}_\texttt{left} tleft 和 t right \texttt{t}_\texttt{right} tright 可能为空。
如果字符串 s \texttt{s} s 有效,则返回 true \texttt{true} true;否则,返回 false \texttt{false} false。
示例
示例 1:
输入:
s
=
"aabcbc"
\texttt{s = "aabcbc"}
s = "aabcbc"
输出:
true
\texttt{true}
true
解释:
""
→
"[abc]"
→
"a[abc]bc"
\texttt{""} \rightarrow \texttt{"[abc]"} \rightarrow \texttt{"a[abc]bc"}
""→"[abc]"→"a[abc]bc"
因此,
"aabcbc"
\texttt{"aabcbc"}
"aabcbc" 有效。
示例 2:
输入:
s
=
"abcabcababcc"
\texttt{s = "abcabcababcc"}
s = "abcabcababcc"
输出:
true
\texttt{true}
true
解释:
""
→
"[abc]"
→
"abc[abc]"
→
"abcabc[abc]"
→
"abcabcab[abc]c"
\texttt{""} \rightarrow \texttt{"[abc]"} \rightarrow \texttt{"abc[abc]"} \rightarrow \texttt{"abcabc[abc]"} \rightarrow \texttt{"abcabcab[abc]c"}
""→"[abc]"→"abc[abc]"→"abcabc[abc]"→"abcabcab[abc]c"
因此,
"abcabcababcc"
\texttt{"abcabcababcc"}
"abcabcababcc" 有效。
示例 3:
输入:
s
=
"abccba"
\texttt{s = "abccba"}
s = "abccba"
输出:
false
\texttt{false}
false
解释:执行操作无法得到
"abccba"
\texttt{"abccba"}
"abccba"。
示例 4:
输入:
s
=
"cababc"
\texttt{s = "cababc"}
s = "cababc"
输出:
false
\texttt{false}
false
解释:执行操作无法得到
"cababc"
\texttt{"cababc"}
"cababc"。
数据范围
- 1 ≤ s.length ≤ 2 × 10 4 \texttt{1} \le \texttt{s.length} \le \texttt{2} \times \texttt{10}^\texttt{4} 1≤s.length≤2×104
- s \texttt{s} s 由字母 ‘a’ \texttt{`a'} ‘a’、 ‘b’ \texttt{`b'} ‘b’ 和 ‘c’ \texttt{`c'} ‘c’ 组成
解法
思路和算法
常规的思路是从空字符串开始,每次将字符串 “abc" \text{``abc"} “abc" 插入某个位置,判断是否可能得到字符串 s s s。由于每次插入字符串 “abc" \text{``abc"} “abc" 的位置有很多,上述做法的时间复杂度会非常高,因此必须考虑其他做法。
注意到每次在字符串 t t t 中插入 “abc" \text{``abc"} “abc" 之后,字符串 t t t 中一定存在三个连续的字符为 “abc" \text{``abc"} “abc",将这三个字符删除以后,字符串 t t t 回到这次插入操作之前的状态,且这三个字符前后的字符从不连续变成连续。
根据题目中的操作,可以定义其反向操作:从字符串 s s s 开始,如果存在三个连续的字符为 “abc" \text{``abc"} “abc",则将这三个字符删除。
如果可以从空字符串开始得到字符串 s s s,则从字符串 s s s 开始,一定可以执行若干次反向操作,将字符串 s s s 变成空。
由于每次反向操作都是删除字符串中的三个连续字符,因此可以使用栈实现。
从左到右遍历字符串 s s s,对于每个字符,如果该字符是 ‘a’ \text{`a'} ‘a’ 或 ‘b’ \text{`b'} ‘b’ 则将该字符入栈,如果该字符是 ‘c’ \text{`c'} ‘c’ 则进行如下操作:
-
如果栈内元素个数小于 2 2 2,返回 false \text{false} false;
-
如果栈内元素个数大于等于 2 2 2,则将 2 2 2 个字符出栈,出栈的 2 2 2 个字符必须依次是 ‘b’ \text{`b'} ‘b’ 和 ‘a’ \text{`a'} ‘a’,否则返回 false \text{false} false。
遍历结束之后,所有的字符 ‘a’ \text{`a'} ‘a’ 和 ‘b’ \text{`b'} ‘b’ 都应该和 ‘c’ \text{`c'} ‘c’ 组合,因此不应该有多余的字符 ‘a’ \text{`a'} ‘a’ 和 ‘b’ \text{`b'} ‘b’。当栈为空时返回 true \text{true} true,否则返回 false \text{false} false。
实现方面有一处可以优化。由于每次在字符串 t t t 中插入 3 3 3 个字符,因此如果字符串 s s s 有效,其长度一定是 3 3 3 的倍数。如果 s s s 的长度不是 3 3 3 的倍数,则不可能通过若干次插入 3 3 3 个字符得到字符串 s s s,直接返回 false \text{false} false。
证明
上述遍历操作中,对于字符 ‘c’ \text{`c'} ‘c’ 的操作可以概括为:只要遇到字符 ‘c’ \text{`c'} ‘c’,就必须和前面相邻的 “ab" \text{``ab"} “ab" 组合,否则字符串 s s s 就是无效的。
由于每次都是插入字符串 “abc" \text{``abc"} “abc",因此字符 ‘c’ \text{`c'} ‘c’ 的前面两个字符一定是 “ab" \text{``ab"} “ab",除非在这三个连续的字符之内( ‘a’ \text{`a'} ‘a’ 和 ‘b’ \text{`b'} ‘b’ 之间或者 ‘b’ \text{`b'} ‘b’ 和 ‘c’ \text{`c'} ‘c’ 之间)有新的插入操作。
从左到右遍历字符串 s s s 的过程中,对于遇到的第一个字符 ‘c’ \text{`c'} ‘c’,其左边没有新的插入操作,因此当前字符 ‘c’ \text{`c'} ‘c’ 的前面两个字符一定是 “ab" \text{``ab"} “ab",否则当前字符 ‘c’ \text{`c'} ‘c’ 无法通过插入字符串 “abc" \text{``abc"} “abc" 的操作进入字符串 s s s,字符串 s s s 是无效的。
当遇到的第一个字符 ‘c’ \text{`c'} ‘c’ 的前面两个字符是 “ab" \text{``ab"} “ab" 时,这三个连续的字符通过一次插入操作进入字符串 s s s,将这三个连续的字符删除,其中的字符 ‘c’ \text{`c'} ‘c’ 也删除,遇到的下一个字符 ‘c’ \text{`c'} ‘c’ 是仍然在字符串中的第一个字符 ‘c’ \text{`c'} ‘c’,因此可以重复上述操作,判断字符串 s s s 中的每个字符 ‘c’ \text{`c'} ‘c’ 是否符合使字符串 s s s 有效的要求。
即使字符串 s s s 中的每个字符 ‘c’ \text{`c'} ‘c’ 都符合使字符串 s s s 有效的要求,字符串 s s s 仍然可能有多余的字符 ‘a’ \text{`a'} ‘a’ 和 ‘b’ \text{`b'} ‘b’,因此只判断字符 ‘c’ \text{`c'} ‘c’ 是不够的,还需要判断是否有多余的字符 ‘a’ \text{`a'} ‘a’ 和 ‘b’ \text{`b'} ‘b’,只有当没有多余的字符 ‘a’ \text{`a'} ‘a’ 和 ‘b’ \text{`b'} ‘b’ 时,字符串 s s s 才是有效的。
代码
class Solution {
public boolean isValid(String s) {
int length = s.length();
if (length % 3 != 0) {
return false;
}
Deque<Character> stack = new ArrayDeque<Character>();
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == 'a' || c == 'b') {
stack.push(c);
} else {
if (stack.size() < 2) {
return false;
}
char c2 = stack.pop();
char c1 = stack.pop();
if (c2 != 'b' || c1 != 'a') {
return false;
}
}
}
return stack.isEmpty();
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。需要遍历字符串 s s s 一次,每个元素最多入栈和出栈各一次。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。空间复杂度主要取决于栈空间,栈内的元素个数为 O ( n ) O(n) O(n)。