classSolution: deffindLongestWord(self, s: str, dictionary: List[str]) -> str: res = "" for t in dictionary: i = j = 0 while i < len(t) and j < len(s): if t[i] == s[j]: i += 1 j += 1 if i == len(t): iflen(t) > len(res) or (len(t) == len(res) and t < res): res = t return res
publicclassSolution { publicstringFindLongestWord(string s, IList<string> dictionary) { string res = ""; foreach (string t in dictionary) { int i = 0, j = 0; while (i < t.Length && j < s.Length) { if (t[i] == s[j]) { ++i; } ++j; } if (i == t.Length) { if (t.Length > res.Length || (t.Length == res.Length && t.CompareTo(res) < 0)) { res = t; } } } return res; } }
[sol1-Golang]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
funcfindLongestWord(s string, dictionary []string) (ans string) { for _, t := range dictionary { i := 0 for j := range s { if s[j] == t[i] { i++ if i == len(t) { iflen(t) > len(ans) || len(t) == len(ans) && t < ans { ans = t } break } } } } return }
[sol1-JavaScript]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
var findLongestWord = function(s, dictionary) { let res = ""; for (const t of dictionary) { let i = 0, j = 0; while (i < t.length && j < s.length) { if (t[i] === s[j]) { ++i; } ++j; } if (i === t.length) { if (t.length > res.length || (t.length === res.length && t < res)) { res = t; } } } return res; };
复杂度分析
时间复杂度:O(d \times (m+n)),其中 d 表示 dictionary 的长度,m 表示 s 的长度,n 表示 dictionary 中字符串的平均长度。我们需要遍历 dictionary 中的 d 个字符串,每个字符串需要 O(n+m) 的时间复杂度来判断该字符串是否为 s 的子序列。
classSolution: deffindLongestWord(self, s: str, dictionary: List[str]) -> str: dictionary.sort(key=lambda x: (-len(x), x)) for t in dictionary: i = j = 0 while i < len(t) and j < len(s): if t[i] == s[j]: i += 1 j += 1 if i == len(t): return t return""
classSolution { public String findLongestWord(String s, List<String> dictionary) { Collections.sort(dictionary, newComparator<String>() { publicintcompare(String word1, String word2) { if (word1.length() != word2.length()) { return word2.length() - word1.length(); } else { return word1.compareTo(word2); } } }); for (String t : dictionary) { inti=0, j = 0; while (i < t.length() && j < s.length()) { if (t.charAt(i) == s.charAt(j)) { ++i; } ++j; } if (i == t.length()) { return t; } } return""; } }
[sol2-Golang]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
funcfindLongestWord(s string, dictionary []string)string { sort.Slice(dictionary, func(i, j int)bool { a, b := dictionary[i], dictionary[j] returnlen(a) > len(b) || len(a) == len(b) && a < b }) for _, t := range dictionary { i := 0 for j := range s { if s[j] == t[i] { i++ if i == len(t) { return t } } } } return"" }
for (const t of dictionary) { let i = 0, j = 0; while (i < t.length && j < s.length) { if (t[i] === s[j]) { ++i; } ++j; } if (i === t.length) { return t; } } return""; };
复杂度分析
时间复杂度:O(d \times m \times \log d + d \times (m+n)),其中 d 表示 dictionary 的长度,m 表示 s 的长度,n 表示 dictionary 中字符串的平均长度。我们需要 O(d \times m \times \log d) 的时间来排序 dictionary;在最坏的情况下,我们需要 O(d \times (m+n)) 来找到第一个符合条件的字符串。
空间复杂度:O(d \times m),为排序的开销。
方法三:动态规划
思路和算法
在方法一的基础上,我们考虑通过对字符串 s 的预处理,来优化第 1 个问题的处理。
考虑前面的双指针的做法,我们注意到我们有大量的时间用于在 s 中找到下一个匹配字符。
这样我们通过预处理,得到:对于 s 的每一个位置,从该位置开始往后每一个字符第一次出现的位置。
我们可以使用动态规划的方法实现预处理,令 f[i][j] 表示字符串 s 中从位置 i 开始往后字符 j 第一次出现的位置。在进行状态转移时,如果 s 中位置 i 的字符就是 j,那么 f[i][j]=i,否则 j 出现在位置 i+1 开始往后,即 f[i][j]=f[i+1][j];因此我们要倒过来进行动态规划,从后往前枚举 i。
for i inrange(m - 1, -1, -1): for j inrange(26): iford(s[i]) == j + 97: f[i][j] = i else: f[i][j] = f[i + 1][j]
res = "" for t in dictionary: match = True j = 0 for i inrange(len(t)): if f[j][ord(t[i]) - 97] == m: match = False break j = f[j][ord(t[i]) - 97] + 1 ifmatch: iflen(t) > len(res) or (len(t) == len(res) and t < res): res = t return res
funcfindLongestWord(s string, dictionary []string) (ans string) { m := len(s) f := make([][26]int, m+1) for i := range f[m] { f[m][i] = m } for i := m - 1; i >= 0; i-- { f[i] = f[i+1] f[i][s[i]-'a'] = i }
outer: for _, t := range dictionary { j := 0 for _, ch := range t { if f[j][ch-'a'] == m { continue outer } j = f[j][ch-'a'] + 1 } iflen(t) > len(ans) || len(t) == len(ans) && t < ans { ans = t } } return }