这道题因为没读懂题被坑了一下,最开始我以为是找到最短的子串经过多次重组拼接甚至是从中间插入可以得到最终的字符串,于是我自信的写下了
int minAnagramLength(string s) {
unordered_map<int, int> arr;
for (auto c : s) {
arr[c]++;
}
int gcd_{arr[s[0]]};
for (auto [k, v] : arr) {
gcd_ = gcd(v, gcd_);
}
return std::accumulate(arr.begin(), arr.end(), 0,
[&](auto v, auto kv) { return v + kv.second / gcd_; });
}
首先统计每个字母出现次数,找到所有数的最大公因数,最后的结果就是每个字母拼接后出现的次数为arr[c]/gcd
,比如“abba”a和b各出现两次gcd为2,accumulate的结果就是2没问题,假如是“aaabbbcccccc”这样的,gcd为3,accumulate的值为1 + 1 + 2,得到的子串为“abcc”每个元素重复三次得到原串,我的想法是这样的,但是提交之后559个测试点过了549个,挂在了“aabb”上,按照我的理论这个最小串应该是“ab”,但是其实不是,这里应该是不能插的,也可能是“ab”、“ab”是相同的字符数不算同位字符串?总之是做错了。
不过尽管题意理解错了但是我觉得以后若真出现这样的题,那么这是一个很好的解决方法,故保留下来。
接下来是正解:
int minAnagramLength(string s) {
int size{static_cast<int>(s.size())};
for (size_t i = 1; i < size; i++) {
// 保证每个切片长度可以被s长度整除
if (size % i == 0) {
int arr[26]{};
// 创建一个以s[0]开始长度为i的string_view
string_view sub{s.data(), i};
// 统计每个字母出现的次数
// 这里其实可以做优化但我懒的改
// 提示: 不用每次都重新统计出arr
for (auto &c : sub) {
arr[c - 'a']++;
}
bool done{true};
for (int j = i; j < size; j += i) {
int t[26];
memcpy(t, arr, 4 * 26);
// 这里是第 n 段切片
string_view sv{s.data() + j, i};
for (auto &c : sv) {
t[c - 'a']--;
}
// 所有字母出现频率不相同?
if (!std::all_of(t, t + 26, [&](int v) { return v == 0; })) {
done = false;
break;
}
}
// 所有切片的每个字母的出现频率都行相同
if (done) {
return i;
}
}
}
return size;
}
这里我使用了C++17引入的string_view
这个类可以大大减小对string的拷贝的开销,具体自学,当然这里控制index也可以,但是我觉得直接分子串好写,也不用写一串判断赋值条件了,这个思路很简单了,就是将整个串分成 n 个等长的子串,判断每个子串每个字母出现频率是否相同,是的话可以直接返回 i ,因为我们是要找最小的结果,i 就是从小到大递增的