目标
给你一个字符串 word。如果 word 中同时出现某个字母 c 的小写形式和大写形式,并且 每个 小写形式的 c 都出现在第一个大写形式的 c 之前,则称字母 c 是一个 特殊字母 。
返回 word 中 特殊字母 的数量。
示例 1:
输入:word = "aaAbcBC"
输出:3
解释:
特殊字母是 'a'、'b' 和 'c'。
示例 2:
输入:word = "abc"
输出:0
解释:
word 中不存在特殊字母。
示例 3:
输入:word = "AbBCab"
输出:0
解释:
word 中不存在特殊字母。
说明:
- 1 <= word.length <= 2 * 10^5
- word 仅由小写和大写英文字母组成。
思路
有一个字符串 word,返回其中大小写同时存在,且 所有小写都在其大写之前出现 的的字母个数。
与 3120_统计特殊字母的数量I 相比,本题多了顺序条件。
使用两个数组标记字母(无论大小写,统一用 0 ~ 25 表示)是否被计数以及是否被删除:
- 如果当前字母是大写:
- 之前大小写都已出现(计数 或者 删除都已经处理过了),直接跳过
- 对应的小写已经出现,计数(上面的条件保证了不会重复计数),并标记为已计数
- 如果当前字母是小写:
- 之前大小写都已出现,且已计数(不一定被计数,因为小写可能后出现)未被标记为已删除,则计数减一,并标记为已删除
- 注意如果之前只出现过大写,不用处理,因为没有被计数
代码
/**
* @date 2026-05-26 9:06
*/
public class NumberOfSpecialChars3121 {
/**
* A(65): 1000001
* Z(90): 1011010
* a(97): 1100001
* z(122): 1111010
* 31: 0011111
* 32: 0100000
*/
public int numberOfSpecialChars_v1(String word) {
Set<Integer> set = new HashSet<>();
int n = word.length();
boolean[] rm = new boolean[26];
boolean[] add = new boolean[26];
int res = 0;
for (int i = 0; i < n; i++) {
int c = word.charAt(i);
// 将字母映射到 0 ~ 25,不区分大小写
int index = (c & 31) - 1;
// flag 为 0 表示大写字母,为 1 是小写字母
int flag = c & 32;
// 如果之前已经遇到过字母的大小写
if (set.contains(c) && set.contains(c ^ (1 << 5))) {
// 如果当前是大写,无需处理,因为需要加的话前面已经加了,需要减的前面已经减了
if (flag == 0){
continue;
}
// 如果当前是小写,计数过且没减过,计数减一,并标记
// 注意,如果之前只遇到过大写,不用处理,因为不满足条件没有被计数
if (!rm[index] && add[index]){
rm[index] = true;
res--;
}
}
// 如果当前是大写并且之前出现过小写,标记并计数
if (flag == 0 && set.contains(c + 32)){
add[index] = true;
res++;
}
set.add(c);
}
return res;
}
}
性能
