Medium

题目描述

给你一个产品数组 products 和一个字符串 searchWord

设计一个推荐系统,在输入 searchWord 的每个字符后,推荐最多三个产品名称。推荐的产品应与 searchWord 有共同前缀。如果有超过三个产品有共同前缀,返回按字典序最小的三个产品。

返回在输入 searchWord 每个字符后建议的产品列表的列表。

示例 1:

输入:products = ["mobile","mouse","moneypot","monitor","mousepad"], searchWord = "mouse"
输出:[["mobile","moneypot","monitor"],["mobile","moneypot","monitor"],["mouse","mousepad"],["mouse","mousepad"],["mouse","mousepad"]]
解释:按字典序排序后 products = ["mobile","moneypot","monitor","mouse","mousepad"]。
输入 m 和 mo 后,所有产品都匹配,显示 ["mobile","moneypot","monitor"]。
输入 mou、mous 和 mouse 后,系统推荐 ["mouse","mousepad"]。

示例 2:

输入:products = ["havana"], searchWord = "havana"
输出:[["havana"],["havana"],["havana"],["havana"],["havana"],["havana"]]
解释:唯一的单词 "havana" 在输入搜索词时总是被建议。

提示:

  • 1 <= products.length <= 1000
  • 1 <= products[i].length <= 3000
  • 1 <= sum(products[i].length) <= 2 * 10^4
  • products 的所有字符串都是唯一的
  • products[i] 由小写英文字母组成
  • 1 <= searchWord.length <= 1000
  • searchWord 由小写英文字母组成

解题思路

这个问题有多种解法:

解法1:排序+双指针(推荐)

最直观的方法是先将产品数组排序,这样符合前缀的产品会连续出现。对于每个前缀,我们可以用双指针找到匹配范围,然后取前三个。

解法2:字典树(Trie)

构建字典树存储所有产品,在每个节点保存按字典序排列的前三个产品。查询时沿着字典树向下遍历即可。

解法3:二分查找

在排序后的数组中,用二分查找找到第一个以当前前缀开头的产品位置,然后取后续最多三个产品。

考虑到题目约束和实现复杂度,排序+双指针是最平衡的方案,代码简洁且效率高。

算法步骤:

  1. 对产品数组排序,确保字典序
  2. 对于每个前缀长度,用双指针维护匹配区间
  3. 在区间内取前三个产品作为推荐结果

代码实现

class Solution {
public:
    vector<vector<string>> suggestedProducts(vector<string>& products, string searchWord) {
        sort(products.begin(), products.end());
        vector<vector<string>> result;
        int left = 0, right = products.size() - 1;
        
        for (int i = 0; i < searchWord.length(); i++) {
            char c = searchWord[i];
            
            // 缩小左边界
            while (left <= right && (products[left].length() <= i || products[left][i] != c)) {
                left++;
            }
            
            // 缩小右边界
            while (left <= right && (products[right].length() <= i || products[right][i] != c)) {
                right--;
            }
            
            // 收集最多3个结果
            vector<string> suggestions;
            for (int j = left; j <= right && j < left + 3; j++) {
                suggestions.push_back(products[j]);
            }
            result.push_back(suggestions);
        }
        
        return result;
    }
};
class Solution:
    def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]:
        products.sort()
        result = []
        left, right = 0, len(products) - 1
        
        for i in range(len(searchWord)):
            c = searchWord[i]
            
            # 缩小左边界
            while left <= right and (len(products[left]) <= i or products[left][i] != c):
                left += 1
            
            # 缩小右边界
            while left <= right and (len(products[right]) <= i or products[right][i] != c):
                right -= 1
            
            # 收集最多3个结果
            suggestions = []
            for j in range(left, min(right + 1, left + 3)):
                suggestions.append(products[j])
            
            result.append(suggestions)
        
        return result
public class Solution {
    public IList<IList<string>> SuggestedProducts(string[] products, string searchWord) {
        Array.Sort(products);
        var result = new List<IList<string>>();
        int left = 0, right = products.Length - 1;
        
        for (int i = 0; i < searchWord.Length; i++) {
            char c = searchWord[i];
            
            // 缩小左边界
            while (left <= right && (products[left].Length <= i || products[left][i] != c)) {
                left++;
            }
            
            // 缩小右边界
            while (left <= right && (products[right].Length <= i || products[right][i] != c)) {
                right--;
            }
            
            // 收集最多3个结果
            var suggestions = new List<string>();
            for (int j = left; j <= right && j < left + 3; j++) {
                suggestions.Add(products[j]);
            }
            result.Add(suggestions);
        }
        
        return result;
    }
}
var suggestedProducts = function(products, searchWord) {
    products.sort();
    const result = [];
    let left = 0, right = products.length - 1;
    
    for (let i = 0; i < searchWord.length; i++) {
        const c = searchWord[i];
        
        // 缩小左边界
        while (left <= right && (products[left].length <= i || products[left][i] !== c)) {
            left++;
        }
        
        // 缩小右边界
        while (left <= right && (products[right].length <= i || products[right][i] !== c)) {
            right--;
        }
        
        // 收集最多3个结果
        const suggestions = [];
        for (let j = left; j <= right && j < left + 3; j++) {
            suggestions.push(products[j]);
        }
        result.push(suggestions);
    }
    
    return result;
};

复杂度分析

复杂度类型大小
时间复杂度O(n log n + m·n)
空间复杂度O(1)

其中 n 是产品数量,m 是搜索词长度。排序需要 O(n log n),查询过程最坏情况下需要 O(m·n)。