Medium

题目描述

给你一个由 n 个非空字符串组成的数组 arr

请你构造一个长度为 n 的字符串数组 answer,满足:

  • answer[i]arr[i] 的最短子字符串,且该子字符串不会作为子字符串出现在数组中的其他字符串中。如果有多个这样的子字符串,answer[i] 应该是字典序最小的。如果不存在这样的子字符串,answer[i] 应该是空字符串。

返回数组 answer

示例 1:

输入:arr = ["cab","ad","bad","c"]
输出:["ab","","ba",""]
解释:
- 对于字符串 "cab",不在其他字符串中出现的最短子字符串是 "ca" 或 "ab",我们选择字典序更小的 "ab"。
- 对于字符串 "ad",没有不在其他字符串中出现的子字符串。
- 对于字符串 "bad",不在其他字符串中出现的最短子字符串是 "ba"。
- 对于字符串 "c",没有不在其他字符串中出现的子字符串。

示例 2:

输入:arr = ["abc","bcd","abcd"]
输出:["","","abcd"]
解释:
- 对于字符串 "abc",没有不在其他字符串中出现的子字符串。
- 对于字符串 "bcd",没有不在其他字符串中出现的子字符串。
- 对于字符串 "abcd",不在其他字符串中出现的最短子字符串是 "abcd"。

提示:

  • n == arr.length
  • 2 <= n <= 100
  • 1 <= arr[i].length <= 20
  • arr[i] 仅由小写英文字母组成

解题思路

这是一个典型的子字符串匹配问题,需要找到每个字符串中独有的最短子字符串。

解题思路:

  1. 暴力枚举法:对于每个字符串,我们需要枚举其所有可能的子字符串,从长度为1开始逐步增加长度。

  2. 哈希表优化:首先预处理所有字符串的所有子字符串,使用哈希表记录每个子字符串出现在哪些字符串中。

  3. 查找策略:对于当前字符串 arr[i],按照子字符串长度从小到大的顺序遍历其所有子字符串。对于相同长度的子字符串,按照字典序从小到大遍历。

  4. 判断条件:一个子字符串是有效的当且仅当它只出现在当前字符串中,不在其他任何字符串中出现。

算法步骤:

  • 构建哈希表,记录所有子字符串及其出现的字符串索引
  • 对于每个字符串,枚举其所有子字符串(按长度和字典序排序)
  • 找到第一个只在当前字符串中出现的子字符串
  • 如果找不到,返回空字符串

这种方法确保了找到的是最短且字典序最小的独有子字符串。

代码实现

class Solution {
public:
    vector<string> shortestSubstrings(vector<string>& arr) {
        int n = arr.size();
        unordered_map<string, vector<int>> substringMap;
        
        // 构建所有子字符串的映射
        for (int i = 0; i < n; i++) {
            string& s = arr[i];
            unordered_set<string> seen;
            for (int j = 0; j < s.length(); j++) {
                for (int k = j; k < s.length(); k++) {
                    string sub = s.substr(j, k - j + 1);
                    if (seen.find(sub) == seen.end()) {
                        seen.insert(sub);
                        substringMap[sub].push_back(i);
                    }
                }
            }
        }
        
        vector<string> result(n);
        
        for (int i = 0; i < n; i++) {
            string& s = arr[i];
            string answer = "";
            
            // 按长度枚举子字符串
            for (int len = 1; len <= s.length() && answer.empty(); len++) {
                vector<string> candidates;
                for (int j = 0; j <= s.length() - len; j++) {
                    string sub = s.substr(j, len);
                    if (substringMap[sub].size() == 1 && substringMap[sub][0] == i) {
                        candidates.push_back(sub);
                    }
                }
                if (!candidates.empty()) {
                    sort(candidates.begin(), candidates.end());
                    answer = candidates[0];
                }
            }
            result[i] = answer;
        }
        
        return result;
    }
};
class Solution:
    def shortestSubstrings(self, arr: List[str]) -> List[str]:
        n = len(arr)
        substring_map = {}
        
        # 构建所有子字符串的映射
        for i in range(n):
            s = arr[i]
            seen = set()
            for j in range(len(s)):
                for k in range(j, len(s)):
                    sub = s[j:k+1]
                    if sub not in seen:
                        seen.add(sub)
                        if sub not in substring_map:
                            substring_map[sub] = []
                        substring_map[sub].append(i)
        
        result = []
        
        for i in range(n):
            s = arr[i]
            answer = ""
            
            # 按长度枚举子字符串
            for length in range(1, len(s) + 1):
                if answer:
                    break
                candidates = []
                for j in range(len(s) - length + 1):
                    sub = s[j:j+length]
                    if len(substring_map[sub]) == 1 and substring_map[sub][0] == i:
                        candidates.append(sub)
                if candidates:
                    candidates.sort()
                    answer = candidates[0]
            
            result.append(answer)
        
        return result
public class Solution {
    public string[] ShortestSubstrings(string[] arr) {
        int n = arr.Length;
        var substringMap = new Dictionary<string, List<int>>();
        
        // 构建所有子字符串的映射
        for (int i = 0; i < n; i++) {
            string s = arr[i];
            var seen = new HashSet<string>();
            for (int j = 0; j < s.Length; j++) {
                for (int k = j; k < s.Length; k++) {
                    string sub = s.Substring(j, k - j + 1);
                    if (!seen.Contains(sub)) {
                        seen.Add(sub);
                        if (!substringMap.ContainsKey(sub)) {
                            substringMap[sub] = new List<int>();
                        }
                        substringMap[sub].Add(i);
                    }
                }
            }
        }
        
        string[] result = new string[n];
        
        for (int i = 0; i < n; i++) {
            string s = arr[i];
            string answer = "";
            
            // 按长度枚举子字符串
            for (int len = 1; len <= s.Length && string.IsNullOrEmpty(answer); len++) {
                var candidates = new List<string>();
                for (int j = 0; j <= s.Length - len; j++) {
                    string sub = s.Substring(j, len);
                    if (substringMap[sub].Count == 1 && substringMap[sub][0] == i) {
                        candidates.Add(sub);
                    }
                }
                if (candidates.Count > 0) {
                    candidates.Sort();
                    answer = candidates[0];
                }
            }
            result[i] = answer;
        }
        
        return result;
    }
}
var shortestSubstrings = function(arr) {
    const n = arr.length;
    const result = new Array(n).fill("");
    
    for (let i = 0; i < n; i++) {
        const str = arr[i];
        let minLen = Infinity;
        let candidate = "";
        
        // Generate all substrings of str
        for (let start = 0; start < str.length; start++) {
            for (let end = start + 1; end <= str.length; end++) {
                const substr = str.substring(start, end);
                
                // Check if this substring appears in any other string
                let found = false;
                for (let j = 0; j < n; j++) {
                    if (j !== i && arr[j].includes(substr)) {
                        found = true;
                        break;
                    }
                }
                
                // If not found in other strings, it's a candidate
                if (!found) {
                    if (substr.length < minLen || 
                        (substr.length === minLen && substr < candidate)) {
                        minLen = substr.length;
                        candidate = substr;
                    }
                }
            }
        }
        
        result[i] = candidate;
    }
    
    return result;
};

复杂度分析

复杂度类型复杂度
时间复杂度O(n × L³)
空间复杂度O(n × L²)

其中 n 是字符串数组的长度,L 是字符串的平均长度。时间复杂度主要来自于枚举所有子字符串(O(L²))和字符串操作(O(L)),空间复杂度来自于存储所有子字符串的哈希表。