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 <= 7
  • licensePlate 由数字、大小写字母或空格 ' ' 组成
  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 15
  • words[i] 由小写英文字母组成

解题思路

这个问题的核心是字符频次统计和匹配。

思路分析:

  1. 预处理车牌号:首先需要统计 licensePlate 中每个字母的出现次数,忽略数字、空格,并转换为小写。

  2. 逐一检查单词:对于 words 中的每个单词,统计其字母频次,然后检查是否能够"补全"车牌号中的所有字母。

  3. 补全条件:一个单词是补全词当且仅当:对于车牌号中的每个字母,该单词中对应字母的出现次数不少于车牌号中的出现次数。

  4. 选择最短:在所有补全词中,选择长度最短的。如果有多个相同长度的最短补全词,选择在数组中最先出现的。

算法步骤:

  • 使用哈希表统计车牌号中字母的频次
  • 遍历每个单词,统计其字母频次
  • 检查单词是否满足补全条件
  • 维护当前找到的最短补全词

时间复杂度主要取决于遍历所有单词和统计字符频次,空间复杂度为常数级别(最多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个字母)