题目
给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:s = “abpcplea”, dictionary = [“ale”,“apple”,“monkey”,“plea”]
输出:“apple”
示例 2:
输入:s = “abpcplea”, dictionary = [“a”,“b”,“c”]
输出:“a”
提示:
- 1 <= s.length <= 1000
- 1 <= dictionary.length <= 1000
- 1 <= dictionary[i].length <= 1000
- s 和 dictionary[i] 仅由小写英文字母组成
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting
暴力双指针
思路
首先想到的是,暴力双指针
- 初始化
res
- 遍历字符串数组
dictionary
,和res
按「长度最长(优先级 1)」及「字典序最小(优先级 2)」比较- 符合则进入双指针判断
- 否则跳过
- 双指针检查
- 使用两个指针 i 和 j 分别代表检查到 s 和 dictionary[x] 中的哪位字符;
- 当 s[i] != dictionary[x],我们使 i 指针右移,直到找到 s 中第一位与 dictionary[x] 对得上的位置,然后当 i 和 j 同时右移,匹配下一个字符;
- 重复步骤 2,直到整个 dictionary[x] 被匹配完。
复杂度分析
时间复杂度:O(d × (m + n)), d表示dictionary长度,m表示s的长度,n表示dictionary中字符串的平均长度。我们需要遍历dictionary中的d个字符串,每个字符串需要(m + n)的时间复杂度的判断
代码
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
char[] sArray = s.toCharArray();
// 初始化res
String res = "";
for (String p: dictionary) {
// 遍历字符串数组 dictionary,和res比较
// 更长或同长更小的进入双指针判断
if (p.length() > res.length() || p.length() == res.length() && p.compareTo(res) < 0) {
char[] pArray = p.toCharArray();
boolean flag = false;
// 双指针判断
for (int i = 0, j = 0; i < sArray.length; i++) {
if (sArray[i] == pArray[j]) j++;
if (j == pArray.length) {
flag = true;
break;
}
}
// 符合则更新res
if (flag) res = p;
}
}
return res;
}
}
排序 + 双指针
思路
在暴力解中,我们需要遍历全部字典中字符串,显然我们可以按「长度最长(优先级 1)」及「字典序最小(优先级 2)」排序,第一个匹配成功的就是长度最大且符合字典序最小的字符串
Collections.sort(list, (a,b)->{
if (a.length() != b.length()) return b.length() - a.length();
return a.compareTo(b);
});
复杂度分析
时间复杂度: O(d×m×logd+d×(m+n)) 排序时间复杂度O(d×m×logd)
代码
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
Collections.sort(dictionary, (a,b)-> {
if (a.length() != b.length()) return b.length() - a.length();
return a.compareTo(b);
});
int n = s.length();
for (String p: dictionary) {
int m = p.length();
boolean flag = false;
for (int i = 0, j = 0; i < n; i++) {
if (s.charAt(i) == p.charAt(j)) j++;
if (j == m) {
flag = true;
break;
}
}
if (flag) {
return p;
}
}
return "";
}
}
动态规划
思路
在暴力解中,我们花费大量时间在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
。
状态转移方程
复杂度分析
时间复杂度:O(26m+d×n)* 动态规划预处理时间复杂度为O(m×∣Σ∣),判断 d 个字符串是否为 s 的子序列的事件复杂度为O(d×n)*。
代码
class Solution {
public String findLongestWord(String s, List<String> dictionary) {
int n = s.length();
int[][] dp = new int[n + 1][26];
Arrays.fill(dp[n], n);
for (int i = n - 1; i >= 0; i--) {
for (int j = 0; j < 26; j++) {
char c = (char)('a' + j);
if (s.charAt(i) == c) {
dp[i][j] = i;
}else {
dp[i][j] = dp[i + 1][j];
}
}
}
String res = "";
for (String t: dictionary) {
int m = t.length();
if (m > res.length() || m == res.length() && t.compareTo(res) < 0) {
for (int i = 0, j = 0; i < m; i++) {
int c = t.charAt(i) - 'a';
if (dp[j][c] == n) {
break;
}
j = dp[j][c] + 1;
if (i == m - 1) {
res = t;
}
}
}
}
return res;
}
}