Easy
题目描述
给你一个字符串 licensePlate 和一个字符串数组 words,请你找出并返回 words 中的 最短补全词。
补全词 是一个包含 licensePlate 中所有的字母的单词。在匹配 licensePlate 中的字母时:
- 忽略
licensePlate中的 数字和空格。 - 不区分大小写。
- 如果某个字母在
licensePlate中出现不止一次,那么该字母在补全词中的出现次数应当一致或者更多。
例如:licensePlate = "aBc 12c",那么它由字母 'a'、'b' (忽略大小写)和两个 'c' 组成。可能的 补全词 有 "abccdef"、"caaacab" 以及 "cbca"。
返回 words 中的 最短补全词。题目数据保证一定存在一个答案。当有多个长度相同的最短补全词时,返回 words 中最靠前的那个。
示例 1:
输入:licensePlate = "1s3 PSt", words = ["step","steps","stripe","stepple"]
输出:"steps"
解释:licensePlate 包含字母 's'、'p'、's'(忽略大小写)和 't'。
"step" 包含 't'、'p',但只包含 1 个 's'。
"steps" 包含 't'、'p' 和两个 's'。
"stripe" 缺一个 's'。
"stepple" 缺一个 's'。
因此,"steps" 是唯一一个包含所有字母的单词,也就是答案。
示例 2:
输入:licensePlate = "1s3 456", words = ["looks","pest","stew","show"]
输出:"pest"
解释:licensePlate 只包含字母 's'。所有的单词都包含字母 's',但其中 "pest"、"stew"、和 "show" 三者最短。答案是 "pest" ,因为它是三个单词中在 words 里最靠前的那个。
提示:
1 <= licensePlate.length <= 7licensePlate由数字、大小写字母或空格' '组成1 <= words.length <= 10001 <= words[i].length <= 15words[i]由小写英文字母组成
解题思路
这个问题的核心是字符频次统计和匹配。
思路分析:
预处理车牌号:首先需要统计
licensePlate中每个字母的出现次数,忽略数字、空格,并转换为小写。逐一检查单词:对于
words中的每个单词,统计其字母频次,然后检查是否能够"补全"车牌号中的所有字母。补全条件:一个单词是补全词当且仅当:对于车牌号中的每个字母,该单词中对应字母的出现次数不少于车牌号中的出现次数。
选择最短:在所有补全词中,选择长度最短的。如果有多个相同长度的最短补全词,选择在数组中最先出现的。
算法步骤:
- 使用哈希表统计车牌号中字母的频次
- 遍历每个单词,统计其字母频次
- 检查单词是否满足补全条件
- 维护当前找到的最短补全词
时间复杂度主要取决于遍历所有单词和统计字符频次,空间复杂度为常数级别(最多26个字母)。
代码实现
class Solution {
public:
string shortestCompletingWord(string licensePlate, vector<string>& words) {
// 统计 licensePlate 中字母的频次
vector<int> plateCount(26, 0);
for (char c : licensePlate) {
if (isalpha(c)) {
plateCount[tolower(c) - 'a']++;
}
}
string result = "";
int minLen = INT_MAX;
for (const string& word : words) {
// 统计当前单词的字母频次
vector<int> wordCount(26, 0);
for (char c : word) {
wordCount[c - 'a']++;
}
// 检查是否为补全词
bool isCompleting = true;
for (int i = 0; i < 26; i++) {
if (plateCount[i] > wordCount[i]) {
isCompleting = false;
break;
}
}
// 更新最短补全词
if (isCompleting && word.length() < minLen) {
minLen = word.length();
result = word;
}
}
return result;
}
};
class Solution:
def shortestCompletingWord(self, licensePlate: str, words: List[str]) -> str:
from collections import Counter
# 统计 licensePlate 中字母的频次
plate_count = Counter()
for c in licensePlate:
if c.isalpha():
plate_count[c.lower()] += 1
result = ""
min_len = float('inf')
for word in words:
# 统计当前单词的字母频次
word_count = Counter(word)
# 检查是否为补全词
is_completing = True
for char, count in plate_count.items():
if word_count[char] < count:
is_completing = False
break
# 更新最短补全词
if is_completing and len(word) < min_len:
min_len = len(word)
result = word
return result
public class Solution {
public string ShortestCompletingWord(string licensePlate, string[] words) {
// 统计 licensePlate 中字母的频次
int[] plateCount = new int[26];
foreach (char c in licensePlate) {
if (char.IsLetter(c)) {
plateCount[char.ToLower(c) - 'a']++;
}
}
string result = "";
int minLen = int.MaxValue;
foreach (string word in words) {
// 统计当前单词的字母频次
int[] wordCount = new int[26];
foreach (char c in word) {
wordCount[c - 'a']++;
}
// 检查是否为补全词
bool isCompleting = true;
for (int i = 0; i < 26; i++) {
if (plateCount[i] > wordCount[i]) {
isCompleting = false;
break;
}
}
// 更新最短补全词
if (isCompleting && word.Length < minLen) {
minLen = word.Length;
result = word;
}
}
return result;
}
}
var shortestCompletingWord = function(licensePlate, words) {
// 统计 licensePlate 中字母的频次
const plateCount = new Array(26).fill(0);
for (const c of licensePlate) {
if (/[a-zA-Z]/.test(c)) {
plateCount[c.toLowerCase().charCodeAt(0) - 97]++;
}
}
let result = "";
let minLen = Infinity;
for (const word of words) {
// 统计当前单词的字母频次
const wordCount = new Array(26).fill(0);
for (const c of word) {
wordCount[c.charCodeAt(0) - 97]++;
}
// 检查是否为补全词
let isCompleting = true;
for (let i = 0; i < 26; i++) {
if (plateCount[i] > wordCount[i]) {
isCompleting = false;
break;
}
}
// 更新最短补全词
if (isCompleting && word.length < minLen) {
minLen = word.length;
result = word;
}
}
return result;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(L + N×M),其中 L 是 licensePlate 的长度,N 是 words 数组的长度,M 是单词的平均长度 |
| 空间复杂度 | O(1),只使用了固定大小的数组存储字符频次(最多26个字母) |