数组中的字符串匹配
约 997 字大约 3 分钟
2025-03-01
1408. 数组中的字符串匹配
给你一个字符串数组 words
,数组中的每个字符串都可以看作是一个单词。请你按 任意 顺序返回 words
中是其他单词的子字符串的所有单词。
如果你可以删除 words[j]
最左侧和/或最右侧的若干字符得到 words[i]
,那么字符串 words[i]
就是 words[j]
的一个子字符串。
示例 1:
输入:words = ["mass","as","hero","superhero"]
输出:["as","hero"]
解释:"as" 是 "mass" 的子字符串,"hero" 是 "superhero" 的子字符串。
["hero","as"] 也是有效的答案。
示例 2:
输入:words = ["leetcode","et","code"]
输出:["et","code"]
解释:"et" 和 "code" 都是 "leetcode" 的子字符串。
示例 3:
输入:words = ["blue","green","bu"]
输出:[]
提示:
1 <= words.length <= 100
1 <= words[i].length <= 30
words[i]
仅包含小写英文字母。- 题目数据 保证 每个
words[i]
都是独一无二的。
包含关系是单向的,不是对称的。被包含者到包含者是一对多的关系,因此被包含者是主体。
暴力枚举
一定要先遍历被包含单词,再遍历大单词,这样可以避免重复。
以["leetcoder","leetcode","od","hamlet","am"]为例,确定od被leetcoder包含后就在外循环中看下一个单词hamlet, 不需要再在内部循环中看后面的leetcode了。
小单词是主体,确定它被包含就可以了,不需要确定它被包含了几次。
class Solution {
public List<String> stringMatching(String[] words) {
List<String> ret = new ArrayList<String>(); // 存储结果的列表
// 遍历字符串数组中的每一个单词,一定要先遍历被包含单词,再遍历大单词,这样可以避免重复。
// 以["leetcoder","leetcode","od","hamlet","am"]为例,
// 确定od被leetcoder包含后就在外循环中看下一个单词hamlet,不需要再在内部循环中看后面的leetcode了。
for (int i = 0; i < words.length; i++) {
// 对于每一个单词,再次遍历整个数组
for (int j = 0; j < words.length; j++) {
// 如果当前单词不是自己并且在其他单词中出现
// contains方法的时间复杂度是O(words[i]\times words[j]),因为对words[i]中的每一个字符,都要遍历words[j]进行检查。
if (i != j && words[j].contains(words[i])) {
// 将当前单词添加到结果列表中
ret.add(words[i]);
// 找到后立即停止内层循环,避免重复添加
break;
}
}
}
return ret; // 返回结果列表
}
}
- 时间复杂度:O(n2×L2),其中 n 是字符串数组的长度,L 是字符串数组中最长字符串的长度。 使用 KMP 字符串匹配算法可以将时间复杂度优化到 O(n2×T),其中 T 是字符串数组中所有字符串的平均长度。
- 空间复杂度:O(1)。返回值不计入空间复杂度。如果使用 KMP 字符串匹配算法,那么对应的空间复杂度为 O(T)。
先遍历大单词,再遍历小单词的话就是这样:
class Solution {
public List<String> stringMatching(String[] words) {
List<String> ret = new ArrayList<String>(); // 存储结果的列表
Set<String> added = new HashSet<String>(); // 存储已经添加过的单词,避免重复
// 遍历字符串数组中的每一个单词
for (int i = 0; i < words.length; i++) {
// 对于每一个单词,再次遍历整个数组
for (int j = 0; j < words.length; j++) {
// 如果当前单词不是自己并且在其他单词中出现
if (i != j && words[i].contains(words[j])) {
// 将当前单词添加到结果列表中
if (!added.contains(words[j])) { // 确保不会重复添加
ret.add(words[j]);
added.add(words[j]); // 标记该单词已添加
}
// 不能break,因为后面可能还有被包含单词,所以每个内循环都是全部遍历。这也是会出现重复添加的原因。
}
}
}
return ret; // 返回结果列表
}
}