一、题目描述(LeetCode 748. 最短补全词)

        给定字符串 licensePlate(含大小写字母、数字、空格等)和单词数组 words,需从 words 中找到长度最短的 “补全词”,满足以下条件:

        1. 包含 licensePlate 中所有字母(忽略非字母字符,不区分大小写,如 'P' 与 'p' 视为同一字母);

        2. 每个字母的出现次数不低于 licensePlate 中对应字母的次数(如 licensePlate 含 2 个 's',补全词中 's' 至少 2 个);

3. 若存在多个同长度最短补全词,返回数组中最早出现的那种。

示例

        输入:licensePlate = "1s3 PSt"words = ["step", "steps", "stripe", "stepple"]

        预处理:licensePlate 过滤非字母、转小写后为 "spt"(含 1 个 's'、1 个 'p'、1 个 't');

        筛选:"step" 包含所有目标字母且长度 4("steps" 长 5、"stripe" 长 6、"stepple" 长 6);

        输出:"step"

二、解题思路

核心逻辑为 “先明确需求,再筛选匹配”,拆解为 4 个聚焦步骤:

步骤 1:预处理 licensePlate

过滤非字母字符,将所有字母转为小写,生成仅含目标字母的 “需求字符串”,明确需匹配的字母种类。

步骤 2:统计字母需求频次

利用英文字母仅 26 个的特性,用大小为 26 的数组统计频次:

1. 初始化数组为 0,代表初始需求次数为 0;

        2. 遍历 “需求字符串”,对每个字符 c 计算索引 idx = c - 'a',将数组对应位置 count[idx] 加 1;

        3. 最终数组直观体现需求(如 "spt" 对应 count[18]=1count[15]=1count[19]=1,其余为 0)。

步骤 3:筛选最短补全词

        遍历 words 数组,对每个单词执行以下操作:

1. 用 26 位数组统计当前单词的字母频次;

2. 验证补全词:若单词各字母频次均≥需求频次,即为补全词;

3. 筛选最短:若当前补全词更短,或为首次找到的补全词,更新结果。

步骤 4:返回结果

遍历结束后返回最短补全词,因按数组顺序遍历,同长补全词会优先保留最早出现的。

三、完整代码完成

基于上述思路,C 语言实现如下,含详细注释:

// 全局变量:存储当前最短补全词(跨函数传递结果)
char* result = NULL;
//统计字符串中26个小写字母的频次
void countLetterFreq(char* str, int freq[26]) {
memset(freq, 0, sizeof(int) * 26);  // 初始化数组为0
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] >= 'a' && str[i] <= 'z') {
int idx = str[i] - 'a';
freq[idx]++;
}
}
}
//验证单词是否为补全词,更新最短结果
void checkAndUpdate(char* word, char* license) {
int wordFreq[26] = {0};
int licenseFreq[26] = {0};
countLetterFreq(word, wordFreq);
countLetterFreq(license, licenseFreq);
// 验证补全词
int isCompleting = 1;
for (int i = 0; i < 26; i++) {
if (wordFreq[i] < licenseFreq[i]) {
isCompleting = 0;
break;
}
}
// 更新最短结果
if (isCompleting) {
int wordLen = strlen(word);
if (result == NULL || wordLen < strlen(result)) {
result = word;
}
}
}
//主函数:返回最短补全词
char* shortestCompletingWord(char* licensePlate, char** words, int wordsSize) {
// 预处理licensePlate
int plateLen = strlen(licensePlate);
char* license = (char*)malloc(sizeof(char) * (plateLen + 1));
int idx = 0;
for (int i = 0; i < plateLen; i++) {
if (isalpha(licensePlate[i])) {
license[idx++] = tolower(licensePlate[i]);
}
}
license[idx] = '\0';                  // 手动加结束符
// 筛选补全词
result = NULL;
for (int i = 0; i < wordsSize; i++) {
checkAndUpdate(words[i], license);
}
free(license);
return result;
}

四、注意事项

C 语言字符串运行细节决定成败,以下 5 个坑点必须规避:

1. 字符串结束符 '\0' 不可漏

        预处理 license 后必须加 license[idx] = '\0'。C 语言字符串以 '\0' 为结束标志,缺少则 strlen 会读取垃圾数据,导致频次统计错误或程序崩溃。

2. 禁止 strlen(NULL),做好空指针防护

   result 初始为 NULL,判断时需先检查 result == NULL,再调用 strlen(result)。直接计算 strlen(NULL) 会触发空指针访问,程序必然崩溃。

3. 数组不可用 = 直接赋值

        C 语言数组名是 “地址常量”,wordFreq = licenseFreq 会编译报错。需通过函数参数传递数组指针,或用 memcpy 复制数组。

4. 动态内存 “申请 - 释放” 需配对

   malloc 分配的 license 必须用 free(license) 释放,否则会导致内存泄漏。free 仅能释放动态内存,不可释放静态数组或字符串常量。

5. 全局变量需每次重置

        多次调用 shortestCompletingWord 前,需将 result 设为 NULL,避免上一次结果干扰当前计算。

六、相似题目推荐

为巩固 “频次统计” 与 C 语言能力,推荐 3 道梯度题目:

1. LeetCode 242. 有效的字母异位词(基础)

判断两字符串是否由相同字母组成,直接复用 26 位数组统计逻辑,对比频次数组即可,适合巩固基础。

2. LeetCode 383. 赎金信(进阶)

        判断 ransomNote 能否由 magazine 构成,无需遍历单词数组,仅对比两字符串频次,练习频次对比的简化实现。

3. LeetCode 49. 字母异位词分组(综合)

        将异位词分组,需结合频次统计与哈希表,将频次数组转化为 “特征键”(如 "a1b2c0..."),提升综合应用能力。

posted on 2025-10-10 13:15  ycfenxi  阅读(3)  评论(0)    收藏  举报