1048. 最长字符串链
难度中等225
给出一个单词数组 words
,其中每个单词都由小写英文字母组成。
如果我们可以 不改变其他字符的顺序 ,在 wordA
的任何地方添加 恰好一个 字母使其变成 wordB
,那么我们认为 wordA
是 wordB
的 前身 。
- 例如,
"abc"
是"abac"
的 前身 ,而"cba"
不是"bcad"
的 前身
词链是单词 [word_1, word_2, ..., word_k]
组成的序列,k >= 1
,其中 word1
是 word2
的前身,word2
是 word3
的前身,依此类推。一个单词通常是 k == 1
的 单词链 。
从给定单词列表 words
中选择单词组成词链,返回 词链的 最长可能长度 。
示例 1:
输入:words = ["a","b","ba","bca","bda","bdca"]
输出:4
解释:最长单词链之一为 ["a","ba","bda","bdca"]
示例 2:
输入:words = ["xbc","pcxbcf","xb","cxbc","pcxbc"]
输出:5
解释:所有的单词都可以放入单词链 ["xb", "xbc", "cxbc", "pcxbc", "pcxbcf"].
示例 3:
输入:words = ["abcd","dbqca"]
输出:1
解释:字链["abcd"]是最长的字链之一。
["abcd","dbqca"]不是一个有效的单词链,因为字母的顺序被改变了。
提示:
1 <= words.length <= 1000
1 <= words[i].length <= 16
words[i]
仅由小写英文字母组成。
贪心 + 模拟(做题第一步先看数据范围)
根据数据范围猜解法 ==> O(n^2)
贪心:字符串词链长度一定是从小到大变长的,可以从短的字符串开始计算是否有前身,长的字符串的前身一定是从短的字符串转移来的
字符串数组根据长度和字典序进行排序,然后暴力枚举word
的每一个前身是否存在,若存在前身,word
的词链长度 = 前身词链长度 + 1,最后返回最大值
class Solution {
public int longestStrChain(String[] words) {
Arrays.sort(words, (a, b) -> a.length() == b.length() ? a.compareTo(b) : a.length() - b.length());
int res = 1;
Map<String, Integer> map = new HashMap<>();
for(String w : words){
int len = 0;
int n = w.length();
for(int i = 0; i < n; i++){
String key = w.substring(0, i) + w.substring(i+1);
if(map.containsKey(key)){
len = Math.max(len, map.get(key));
}
}
map.put(w, len+1);
res = Math.max(res, len+1);
}
return res;
}
}
动态规划
模拟中中间存在枚举的过程
-
存在则
长度=前身+1
-
不存在则
长度=1
可以用动态规划来求解
https://leetcode.cn/problems/longest-string-chain/solution/jiao-ni-yi-bu-bu-si-kao-dong-tai-gui-hua-wdkm/
对于字符串 s 来说,假设它是词链的最后一个单词,那么去掉 s 中的一个字母,设新字符串为 t,问题就变成计算以 t 结尾的词链的最长长度。由于这是一个和原问题相似的子问题,因此可以用递归解。
记忆化搜索:
class Solution {
Map<String, Integer> ws = new HashMap<>();
public int longestStrChain(String[] words) {
// 为快速判断字符串是否在words中,需要将所有字符串存入哈希表中
for(String s : words){
ws.put(s, 0); // 0表示未被计算
}
int res = 0;
for(String s : ws.keySet()){
res = Math.max(res, dfs(s));
}
return res;
}
// 定义dfs(s)表示以s结尾的词链的最长长度
public int dfs(String s){
int res = ws.get(s);
if(res > 0) return res; // 之前计算过
for(int i = 0; i < s.length(); i++){
// 由于字符串长度不超过16,暴力枚举去掉的字符
String t = s.substring(0, i) + s.substring(i+1);
if(ws.containsKey(t)){// t 在 words 中
res = Math.max(res, dfs(t));
}
}
ws.put(s, res + 1); // 记忆化
return res+1;
}
}
转成递推:
对于本题,只需要把递归改成循环。
由于我们总是从短的字符串转移到长的字符串,所以要先把字符串按长度从小到大排序,然后从短的开始递推。
- 震惊:居然和 贪心 + 枚举 的解法一样
class Solution {
public int longestStrChain(String[] words) {
Arrays.sort(words, (a, b) -> a.length() - b.length());
int res = 0;
Map<String, Integer> map = new HashMap<>();
for(String w : words){
int len = 0;
for(int i = 0; i < w.length(); i++){
String key = w.substring(0, i) + w.substring(i+1);
len = Math.max(len, map.getOrDefault(key, 0));
}
map.put(w, len+1);
res = Math.max(res, len+1);
}
return res;
}
}