「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战」。
题目
把字符串 s 看作是 “abcdefghijklmnopqrstuvwxyz” 的无限环绕字符串,所以 s 看起来是这样的: "...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd...." .
现在给定另一个字符串 p 。返回 s 中 唯一 的 p 的 非空子串 的数量 。
示例 1:
输入:p = "a"
输出:1
解释:字符串 s 中只有一个"a"子字符。
示例 2:
输入:p = "cac"
输出:2
解释:字符串 s 中的字符串“cac”只有两个子串“a”、“c”。.
示例 3:
输入:p = "zab"
输出:6
解释:在字符串 s 中有六个子串“z”、“a”、“b”、“za”、“ab”、“zab”。
思路
动态规划的题目,不过这题比起一般的dp,更需要一些经验。
首先理解题意,要求p是s的字串,s是一个无限环绕的,连续的,所以p也必须按照字符顺序连续,即类似m后面是n,j后面是k这样的,跟字符顺序唯一的不同就是z后面是a。
理解了题意之后,我们想怎么求出唯一的p的数量。
方案一:
如果我们找到了连续的字串,假设是abc,长度为3,那么我们发现在abc这个字串里面,
- 长度为1的子串有3个:a、b、c
- 长度为2的子串有2个:ab、bc
- 长度为3的子串有1个:abc 所以,如果找到长度为3的字串,那么实际对应的字串数量是1+2+3=3(3+1)/2=6;推广到长度为n的字串,实际对应的字串数量为1+2+...+n=n(n+1)/2。 这样,问题就演化成了,在p中寻找这样连续字串,找到后用高斯求和求出是对应字串的数量,再求和即可。题目还有一个要求是唯一,所以,这里还要去重,可以使用哈希表做,不过空间和时间代价很大,每个长度为n的字串,都要对应处理n(n+1)/2次,也需要对应n(n+1)/2的空间。
方案二:
我们定义一个一维int数组dp,dp[index]代表以'a'+index作为结尾的连续字串最大长度。
这个方法最重要的一个点:以字符char为结尾的连续字串最大长度就是以字符char为结果的不重复字串数量
例如以b结尾的连续字串最大长度是3,那么对应的3个不重复字串就是b、ab、zab。因为dp每个元素代表不同的结尾,所以dp之间不会重复;因为1个dp中len个字串长度不同,所以不会重复;因为字串的结尾可能就是a-z,所以dp没有遗漏;综合起来,对dp求和就是s 中唯一的p的非空子串的数量。
剩下的问题就是怎么求解dp,我们可以遍历p,找到每段连续的字串p~,然后求出p~的长度len,结尾的字串是char,那么状态转移方程就是
dp[chan-'a'] = max(dp[char-'a'],len)
Java版本代码
class Solution {
public int findSubstringInWraproundString(String p) {
// dp用于记录以'a'+index作为结尾的连续字串最大长度
int dp[] = new int[26];
int len = 0;
// 随便给一个初始值,下面循环中会更新
char pre = 'z';
for (int i = 0; i < p.length(); i++) {
char current = p.charAt(i);
if (i > 0 && isContinue(pre, current)) {
len++;
} else {
len = 1;
}
dp[current-'a'] = Integer.max(dp[current-'a'], len);
pre = current;
}
int ans = Arrays.stream(dp).sum();
return ans;
}
private static boolean isContinue(char a, char b) {
if (b - a == 1) {
return true;
}
if (a == 'z' && b == 'a') {
return true;
}
return false;
}
}