给你一个下标从 0 开始的字符串 word
,字符串只包含小写英文字母。你需要选择 一个 下标并 删除 下标处的字符,使得 word
中剩余每个字母出现 频率 相同。
如果删除一个字母后,word
中剩余所有字母的出现频率都相同,那么返回 true
,否则返回 false
。
注意:
- 字母
x
的 频率 是这个字母在字符串中出现的次数。 - 你 必须 恰好删除一个字母,不能一个字母都不删除。
示例 1:
输入:word = "abcc"
输出:true
解释:选择下标 3 并删除该字母,word 变成 "abc" 且每个字母出现频率都为 1 。
示例 2:
输入:word = "aazz"
输出:false
解释:我们必须删除一个字母,所以要么 "a" 的频率变为 1 且 "z" 的频率为 2 ,要么两个字母频率反过来。所以不可能让剩余所有字母出现频率相同。
提示:
2 <= word.length <= 100
word
只包含小写英文字母。
解法1 暴力枚举删除的字符
枚举 w o r d [ i ] word[i] word[i] ,将其去掉后再统计剩余字符的出现次数。用数组或者哈希表统计都行。如果剩余字符的出现次数都相同,则返回 true
。反之,如果无论去掉哪个 w o r d [ i ] word[i] word[i] ,都无法让剩余字符的出现次数都相同,则返回 false
。
class Solution {
bool isSame(unordered_map<char, int> &cnt) {
int c0 = cnt.begin()->second;
for (auto &[_, c] : cnt)
if (c != c0) return false;
return true;
}
public:
bool equalFrequency(string word) {
for (int i = 0, n = word.size(); i < n; ++i) { // 枚举删除的字符
unordered_map<char, int> cnt;
for (int j = 0; j < n; ++j)
if (j != i) ++cnt[word[j]]; // 统计出现次数
if (isSame(cnt)) // 出现次数都一样
return true;
}
return false;
}
};
复杂度分析:
- 时间复杂度: O ( n 2 ) O(n^2) O(n2) ,其中 n 为 w o r d word word 长度。
- 空间复杂度: O ( ∣ Σ ∣ ) O(|\Sigma|) O(∣Σ∣),其中 ∣ Σ ∣ |\Sigma| ∣Σ∣ 为字符集的大小,本题字符均为小写字母,所以 ∣ Σ ∣ = 26 |\Sigma| =26 ∣Σ∣=26 。
解法2 分类讨论
如果有至少三种不同的出现次数,无论去掉哪个字符,剩下的出现次数仍然至少有两种。所以只需要讨论出现次数至多两种的情况:
- 如果出现次数只有一种:
- 如果只有一种字符,例如 "aaaaa" \texttt{"aaaaa"} "aaaaa" ,那么无论删除哪个都是满足要求的。
- 如果每个字符恰好出现一次,例如 "abcde" \texttt{"abcde"} "abcde" ,那么无论删除哪个都是满足要求的。代码实现时,可以合并到下面的「较少的出现次数恰好是 1 1 1 」的情况中。
- 如果每个字符出现不止一次,例如 "aabbcc" \texttt{"aabbcc"} "aabbcc" ,虽然出现次数均为 2 2 2 ,但题目要求恰好去掉一个字符,所以无法满足要求。
- 如果出现次数有两种,那么必须变成一种:
- 考虑去掉出现次数较少的字符:它的出现次数必须恰好是 1 1 1 ,且只有这一种字符出现一次。例如 "abbbccc" \texttt{"abbbccc"} "abbbccc" ,去掉只出现一次的字符,就能满足要求。但如果它的出现次数大于 1 1 1 ,例如 "aabbbccc" \texttt{"aabbbccc"} "aabbbccc" ,就无法满足要求。
- 考虑去掉出现次数较多的字符:它的出现次数必须比出现次数较少的恰好多 1 1 1,且只有这一种字符出现一次。例如 "aabbccc" \texttt{"aabbccc"} "aabbccc" 去掉 c \texttt{c} c 是可以满足要求的。其它情况如 "abccc" \texttt{"abccc"} "abccc" 或 "abccdd" \texttt{"abccdd"} "abccdd" 都是无法满足要求的。
class Solution {
public:
bool equalFrequency(string word) {
unordered_map<char, int> rec;
for (char c : word) ++rec[c];
vector<int> cnt;
for (auto &[_, c] : rec) cnt.push_back(c);
sort(cnt.begin(), cnt.end()); // 出现次数从小到大排序
// 只有1种字符 or 去掉次数最少的 or 出掉次数最多的
return cnt.size() == 1 ||
cnt[0] == 1 && equal(cnt.begin() + 2, cnt.end(), cnt.begin() + 1) ||
cnt.back() == cnt[cnt.size() - 2] + 1 && equal(cnt.begin() + 1, cnt.end() - 1, cnt.begin());
}
};
复杂度分析:
- 时间复杂度: O ( n + ∣ Σ ∣ log ∣ Σ ∣ ) O(n+|\Sigma|\log |\Sigma|) O(n+∣Σ∣log∣Σ∣) ,其中 n 为 w o r d word word 长度, ∣ Σ ∣ |\Sigma| ∣Σ∣ 为字符集的大小,本题字符均为小写字母,所以 ∣ Σ ∣ = 26 |\Sigma| =26 ∣Σ∣=26 。
- 空间复杂度: O ( ∣ Σ ∣ ) O(|\Sigma|) O(∣Σ∣) 。
是可以优化到 O ( n ) O(n) O(n) 复杂度的,但对本题来说没有太多意义:
class Solution {
public:
bool equalFrequency(string word) {
int cnt[26]{};
for(char c : word) ++cnt[c-'a'];
int maxCnt = 0, minCnt = word.size();
int allCntCnt = 0; // 出现的字符种数
for(int i = 0; i < 26; ++i) {
if (cnt[i]) {
++allCntCnt;
maxCnt=max(cnt[i], maxCnt);
minCnt=min(cnt[i], minCnt);
}
}
// (出现次数只有一种): 所有字符出现次数为1或只有一种字符的情况
if (allCntCnt == 1 || maxCnt == 1) return true;
int maxCntCnt = 0; // 出现次数最多的字符种数
for(int i = 0; i < 26; ++i)
if (cnt[i] == maxCnt)
++maxCntCnt;
// 出现次数最大的字符和出现次数最少的字符仅相差1次, 且只有一种出现次数最大的字符
// 或者出现次数最少的字符次数为1次, 且其它字符出现次数均相同
return (maxCnt - minCnt == 1 && maxCntCnt == 1) ||
(minCnt == 1 && maxCntCnt == allCntCnt - 1);
}
};