动态规划解题步骤:
1.确定状态表示:dp[i]是什么
2.确定状态转移方程:dp[i]等于什么
3.初始化:确保状态转移方程不越界
4.确定填表顺序:根据状态转移方程即可确定填表顺序
5.确定返回值
题目链接:467. 环绕字符串中唯一的子字符串 - 力扣(LeetCode)
题解:
1.状态表示: dp[i]表示以s[i]结尾的字符串中在base中出现的个数
2.状态转移方程:if(s[i-1]+1=s[i]||(s[i-1]=='z'&&s[i]=='a')) dp[i]=dp[i-1]+1
else dp[i]=1
解释:s[i]结尾的字符串有两种状态:1.仅由s[i]组成2.s[i]和前面的字符串组合
base是环绕字符串 ...a...z...a...z... 所以状态1的字符串一定在base出现,dp[i]=1;而状态2如果也有字符串出现在base中,则以s[i-1]结尾的字符串一定也在base中出现过,即s[i-1]字符和s[i]字符一定是连续的(包括环绕的),所以dp[i]=dp[i-1]+1,加1是因为要算上单独的字符s[i]组成的字符串
3.初始化:dp[0]=1 单个字符组成的字符串一定在base出现
4.填表顺序:从左向右依次填写
5.返回值:由于本题要求的是不同非空子串,所以需要去重:例如字符串cbc,其中以c结尾的字符串由两个"c"。如何去重?以c结尾的字符串中,以第一个c结尾的字符串对应的dp[i]=1,以第二个c结尾的字符串对应的dp[i]=2,已经包含了以第一个c结尾的字符串的情况,所以对于相同字符结尾的字符串,只需要取较大的dp[i]即可。
设置一个大小为26的数组hash,遍历字符串s,将dp表中的值按其相应的结尾字符顺序填入hash数组中,分别对应a...z,每次填入时比较hash中已存在的值和要填入的dp[i]取较大值(hash数组中初始值为0),即可完成去重。最后返回hash数组中元素总和即可。
class Solution {
public:
int findSubstringInWraproundString(string s) {
//dp[i]表示以s[i]结尾的字符串中在base中出现的个数
//if(s[i-1]+1=s[i]||(s[i-1]=='z'&&s[i]=='a'))
//dp[i]=dp[i-1]+1
//else dp[i]=1
size_t n=s.size();
//处理边界条件
if(n==1) return 1;
//创建dp表
vector<int> dp(n);
//初始化
dp[0]=1;
//填表
for(int i=1;i<n;++i)
{
if(s[i-1]+1==s[i]||(s[i-1]=='z'&&s[i]=='a'))
{
dp[i]=dp[i-1]+1;
}
else
{
dp[i]=1;
}
}
//重点:去重
int hash[26];
for(int i=0;i<n;++i)
{
hash[s[i]-'a']=max(hash[s[i]-'a'],dp[i]);
}
//返回值
int ans=0;
for(auto e:hash) ans+=e;
return ans;
}
};