题目描述
知识点
动态规划,类似于LIS,只是上升的规则有点复杂罢了。
结果
实现
码前思考
- 这个问题与LIS的最最基本的思想是类似的,只不过这里不要求是子串了,这里可以乱序的,而LIS要求必须是子串。比如:
我之前写的时候,以为跟LIS是一样的,必须是子串,但是事实证明,我过不了题,偷看了测试用例,发现居然可以乱序。。。Input:["aaa","aa","a"] output:3
- 我继续按照动态规划的解题框架来思考:
- 划分子问题:求
words[i]
的最长字符串链,等价于求words[i]
的前驱words[x]们
的最长字符串链中的最大值;由“前驱”的概念,各个子问题之间是独立的。同时,由于前驱的长度为word[i].size()-1
,所以我们有必要对words
按字符串长度进行排序。 - 状态定义:
words
数组的下标; dp[i]
的含义:words[i]
的最长字符串链;- base case:没有前驱的都是1。
代码实现
bool cmp(string a,string b){
if(a.size()<b.size()){
return true;
}
else{
return false;
}
}
class Solution {
private:
//hash[i]存储长度为i的word的下标
vector<vector<int>> hash;
//存储每个word的longest string chain
vector<int> dp;
public:
int longestStrChain(vector<string>& words) {
hash.assign(17,vector<int>());
dp.assign(words.size(),1);
//进行长度的排序
sort(words.begin(),words.end(),cmp);
//开始进行dp
for(int i=0;i<words.size();++i){
//记录当前word的长度
int len = words[i].size();
for(auto j : hash[len-1]){
//是否是前驱,默认为是
bool isPre = true;
//skip代表是否已经跳过了一格
bool skip = false;
for(int k=0,l=0;k<len&&l<len-1;){
//如果两个字符不相等
if(words[i][k] != words[j][l]){
//判断是否已经跳了一格
if(!skip){ //如果没有跳一个
skip = true;
k++;
}else{ //已经跳了一格
isPre = false;
break;
}
}else{//相等的话,匹配下一个
l++;
k++;
}
}
if(isPre){ //判断是不是前驱
dp[i] = max(dp[i],dp[j]+1);
}
}
//当前字符压栈
hash[len].push_back(i);
}
int ans = -1;
//遍历得到Longest string chain
for(int i=0;i<words.size();i++){
ans = max(ans,dp[i]);
}
return ans;
}
};
码后思考
-
如何实现原问题与子问题的状态转移呢?
之前看文章说,子问题到原问题的状态转移就是暴力穷举的抽象,在这里,我们要得到原问题的子问题,需要:
- 把
words
里的所有word
按长度进行一个排序,确保长度短的都在长度长的之前被计算,体现递推思想. - 判断长度为
words[i].size()-1
的字符串是否是words[i]
的前驱。这里直接暴力求解,一个一个比较。由于有长度关系,所以还是很好比较的。 - 还有就是,这样的暴力居然没有超时,我一直担心会超时,这个时间复杂度我其实不太会算,但是我看最不可能的极限1000100015*15都才刚刚超过108,我就用了。。。
- 把
二刷思考
- 这里所涉及的将相同长度的
word
放到同一个vector
中的思想值得学习! 这样就能够使用LIS来巧妙地求解了,而不是暴力地求解不必要的情况; - 这里也涉及到了集合划分的思想,考虑到只有少一个字母的串才可能是自己的前驱~减少不必要判断。