问题解构与核心逻辑
给定一个字符串数组 words,要求返回一个列表,其中包含在所有字符串中都出现的字符。字符区分大小写,题目说明中均为小写字母。关键约束如下:
- 共有性 :结果中的每个字符必须在数组中的每一个字符串里都出现过。
- 最小频率 :若一个字符在不同字符串中出现的次数不同,则它在结果中出现的次数应为在所有字符串中出现次数的最小值。
- 输出 :结果以字符串列表(
vector<string>)形式返回,每个字符作为一个独立的字符串元素。
例如,输入 words = ["bella","label","roller"],字符 'l' 在三个字符串中分别出现2、2、2次,因此结果中加入两个 "l";字符 'e' 分别出现1、1、1次,结果中加入一个 "e"。输出为 ["e","l","l"]。
C++解法:数组计数法
由于字符限定为小写字母,可以使用长度为26的整型数组作为哈希表来统计字符频率,这是最高效的方法。
算法步骤
- 初始化最小频率数组 :创建一个
vector<int> minFreq(26, INT_MAX),用于记录每个字符('a' 到 'z')在所有字符串中的最小出现次数。初始值设为极大值,便于后续取最小值。 - 遍历字符串数组 :
- 对每个字符串
word,创建一个临时数组charCount(26, 0)统计其字符频率。 - 遍历
word中的每个字符,对应charCount索引(c - 'a')加1。 - 遍历26个字母,将
minFreq的每个位置更新为min(minFreq[i], charCount[i])。
- 对每个字符串
- 构建结果 :遍历
minFreq数组,将出现次数大于0的字符,按其最小频率值,重复转换为字符串并添加到结果向量中。
C++代码实现
cpp
#include <vector>
#include <string>
#include <algorithm>
#include <climits> // 用于INT_MAX
using namespace std;
class Solution {
public:
vector<string> commonChars(vector<string>& words) {
// 步骤1:初始化最小频率数组
vector<int> minFreq(26, INT_MAX);
// 步骤2:遍历所有单词,更新最小频率
for (const string& word : words) {
vector<int> charCount(26, 0);
// 统计当前单词的字符频率
for (char c : word) {
charCount[c - 'a']++;
}
// 更新全局最小频率
for (int i = 0; i < 26; ++i) {
minFreq[i] = min(minFreq[i], charCount[i]);
}
}
// 步骤3:根据最小频率构建结果
vector<string> result;
for (int i = 0; i < 26; ++i) {
// 将字符 (char)('a' + i) 重复 minFreq[i] 次加入结果
for (int j = 0; j < minFreq[i]; ++j) {
// 使用 emplace_back 直接在容器末尾构造字符串,效率优于 push_back
result.emplace_back(1, 'a' + i);
}
}
return result;
}
};
代码关键点解析:
minFreq初始化为INT_MAX:确保第一次与charCount取min时,能正确更新为charCount的值。- 内层循环
for (char c : word):遍历字符串的简便写法。 c - 'a':将字符 'a'-'z' 映射到数组索引 0-25 的标准方法。result.emplace_back(1, 'a' + i):这是构建结果的关键。emplace_back直接在result向量的末尾构造一个字符串对象,该字符串由1个字符('a' + i)组成。这比先创建临时字符串再用push_back更高效。
复杂度分析
- 时间复杂度 :O(N * M) ,其中
N是数组words的长度,M是数组中字符串的平均长度。我们需要遍历每个字符串的每个字符两次(一次统计,一次更新最小值),但常数项可以忽略,仍为线性复杂度。 - 空间复杂度 :O(1) 或 O(∣Σ∣) ,其中
∣Σ∣是字符集大小(此处为26)。我们只使用了固定大小的辅助数组,与输入数据规模无关。
与其他解法的对比
为了更全面理解,下表将本解法与另一种常见的哈希表(unordered_map)解法进行对比:
| 特性 | 数组计数法(推荐) | 哈希表法(unordered_map) |
|---|---|---|
| 数据结构 | 固定大小的 vector<int>(大小26) |
unordered_map<char, int> |
| 适用场景 | 字符集已知且较小(如小写字母) | 字符集未知或较大(如Unicode) |
| 时间复杂度 | O(N * M),访问数组为O(1) | O(N * M),哈希表操作平均O(1) |
| 空间复杂度 | O(1) | O(∣Σ∣),但常数开销更大 |
| 性能 | 更优。内存连续,CPU缓存友好,无哈希冲突开销。 | 稍差。涉及哈希计算和动态内存管理。 |
| 代码简洁性 | 简单直观 | 相对复杂,需要处理键不存在的情况 |
对于本题(仅小写字母),数组计数法是绝对最优选择,其效率远高于哈希表法。
关联题目与模式总结
此题的本质是求多个集合(每个字符串的字符频次)的交集,且交集中元素的重数取其在各集合中出现次数的最小值。这种"计数取最小"的模式可以推广到一系列问题:
| 题目 | 核心操作 | 与本题的关联 |
|---|---|---|
| 1002. 查找常用字符 | 求多个字符串的字符频率最小交集 | 本题本身 |
| 349. 两个数组的交集 | 求两个数组的唯一公共元素集合 | 简化版,只求是否存在,不统计次数 |
| 350. 两个数组的交集 II | 求两个数组的公共元素,并保留最小出现次数 | 可以看作本题在两个"字符串"(数组)上的特例 |
| 242. 有效的字母异位词 | 比较两个字符串的字符频率是否完全相同 | 使用相同的数组计数技术 |
掌握这种基于固定数组的频次统计与比较方法,是解决许多字符串处理与哈希类问题的基础技能。