如果某个字符串中 至多一个 字母出现 奇数 次,则称其为 最美 字符串。
- 例如,
"ccjjc"
和"abab"
都是最美字符串,但"ab"
不是。
给你一个字符串 word
,该字符串由前十个小写英文字母组成('a'
到 'j'
)。请你返回 word
中 最美非空子字符串 的数目。如果同样的子字符串在 word
中出现多次,那么应当对 每次出现 分别计数。
子字符串 是字符串中的一个连续字符序列。
示例 1:
输入:word = "aba" 输出:4 解释:4 个最美子字符串如下所示: - "aba" -> "a" - "aba" -> "b" - "aba" -> "a" - "aba" -> "aba"
示例 2:
输入:word = "aabb" 输出:9 解释:9 个最美子字符串如下所示: - "aabb" -> "a" - "aabb" -> "aa" - "aabb" -> "aab" - "aabb" -> "aabb" - "aabb" -> "a" - "aabb" -> "abb" - "aabb" -> "b" - "aabb" -> "bb" - "aabb" -> "b"
示例 3:
输入:word = "he" 输出:2 解释:2 个最美子字符串如下所示: - "he" -> "h" - "he" -> "e"
提示:
1 <= word.length <= 10^5
word
由从'a'
到'j'
的小写英文字母组成
提示 1
For each prefix of the string, check which characters are of even frequency and which are not and represent it by a bitmask.
提示 2
Find the other prefixes whose masks differs from the current prefix mask by at most one bit.
解法:状态压缩 + 前缀数组
提示 1
如果字符串 word 的某个子串 word[i..j] 是最美字符串,那么其中最多只有一个字符出现奇数次,这说明:
对于任意一次字符 c 而言,word 的 i−1 前缀 word[0..i−1] 与 j 前缀 word[0..j] 中字符 c 的出现次数必须同奇偶。同时,我们最多允许有一个字符 c,它在两个前缀中出现次数的奇偶性不同。
提示 2
由于题目保证了 word 中只会包含前 10 个小写字母,因此我们可以用一个长度为 10 的二进制数 mask 表示 word 的前缀中 [a,j] 出现次数的奇偶性,其中 mask 的第 i 位为 1 表示第 i 个字母出现了奇数次,0 表示第 i 个字母出现了偶数次。
记 word 的 k 前缀 word[0..k] 对应的二进制数为 mask k 。
根据提示 1,word[i..j] 是最美字符串,当且仅当 mask i−1 和 mask j 的二进制表示最多只有一位不同。 特别地,如果 i=0,那么 mask −1 =0,即所有字母均未出现过。
思路与算法
我们对字符串 word 进行一次遍历。 当我们遍历到 word[i] 时,我们首先计算出 mask i ,再遍历 mask i 的 10 个二进制位,将其翻转(从 0 变为 1 或者从 1 变为 0)得到 mask i ′ 。
此时 mask i 和 mask i ′ 的二进制表示恰好有一位不同。
为了快速计算答案,我们需要使用一个哈希映射 freq 存储每一个 mask 出现的次数。
这样一来,我们直接将答案增加 freq[mask i ′ ] 即可。
此外,我们还需要将答案增加 freq[mask i ],即两个前缀出现字母的奇偶性完全相同。
同类题型详解:LeetCode 1177. 构建回文串检测-CSDN博客
LeetCode 1371. 每个元音包含偶数次的最长子字符串-CSDN博客
LeetCode 1542. 找出最长的超赞子字符串-CSDN博客
Java版:
class Solution {
public long wonderfulSubstrings(String word) {
long ans = 0;
int status = 0;
int[] count = new int[1 << 10];
count[0] = 1;
for (int i = 0; i < word.length(); i++) {
status ^= 1 << (word.charAt(i) - 'a');
ans += count[status];
for (int j = 0; j <= 9; j++) {
int tmp = status ^ (1 << j);
ans += count[tmp];
}
count[status]++;
}
return ans;
}
}
Python3版:
class Solution:
def wonderfulSubstrings(self, word: str) -> int:
ans = 0
mask = 0
count = [0] * (1 << 10)
count[0] = 1
for c in word:
mask ^= 1 << ord(c) - ord('a')
ans += count[mask]
for j in range(10):
tmp = mask ^ (1 << j)
ans += count[tmp]
count[mask] += 1
return ans
复杂度分析
- 时间复杂度:O(n*A),其中 n 是字符串 s 的长度,A 表示字符集,在本题中字符串只包含 10 个字符,A=10。
- 空间复杂度:O(2^A),即为哈希映射使用的空间。A表示 10 个字符压缩成一个状态数的最大值,在本题中 A=10。我们需要对应 2^10 即 1024 大小的空间来存放每个状态第一次出现的位置.
解法2:状态压缩 + 哈希表
Java版:
class Solution {
public long wonderfulSubstrings(String word) {
long ans = 0;
int status = 0;
Map<Integer, Integer> count = new HashMap<>();
count.put(0, 1);
for (int i = 0; i < word.length(); i++) {
status ^= 1 << (word.charAt(i) - 'a');
ans += count.getOrDefault(status, 0);
for (int j = 0; j <= 9; j++) {
int tmp = status ^ (1 << j);
ans += count.getOrDefault(tmp, 0);
}
count.merge(status, 1, Integer::sum);
}
return ans;
}
}
Python3版:
class Solution:
def wonderfulSubstrings(self, word: str) -> int:
ans = 0
mask = 0
count = {0: 1}
for c in word:
mask ^= 1 << ord(c) - ord('a')
ans += count[mask] if mask in count else 0
for j in range(10):
tmp = mask ^ (1 << j)
ans += count[tmp] if tmp in count else 0
count[mask] = count[mask] + 1 if mask in count else 1
return ans
复杂度分析
- 时间复杂度:O(n*A),其中 n 是字符串 word 的长度,A 是字符集,在本题中 A=10。
- 空间复杂度:O(min(n,2^A))。哈希映射中存储的键值对个数由字符串 word 的长度 n 以及 A 位二进制数的总数 2^A 共同限制,因此空间复杂度为 O(min(n,2^A))。