题目陈述
大意:给定一个字符串仅由’5’、‘2’ 、‘0’ 和‘?’组成,其中’?'可以代表任何一个字符,所有可能的字符串中,位置不同的为 “520” 的子序列共有多少个?
算法一:朴素算法
算法思路
- 最暴力的想法,直接枚举“520”的所有可能
- 第一层循环枚举5和?,第二层枚举2和?,第三层循环枚举0和?
- 当然,此处枚举时,要注意’2’的位置一定得在‘5’的后面,‘0’得在‘2’的后面
代码实现
typedef long long LL; //简化声明定义long long
class Solution
{
public:
LL a, b, c, md = 998244353;
int findOccurrences(string s)
{
LL ans = 0; //答案
int len = s.size(); // 字符串长度
for (int i = 0; i < len ; i ++ )
if(s[i] == '5' || s[i] == '?') //找到“5”
for (int j = i + 1; j < len; j ++ )
if(s[j] == '2' || s[j] == '?') //找到“52”
for(int k = j + 1; k < len; k ++ )
if(s[k] == '0' || s[k] == '?') // 找到“520”
ans = (ans + 1) % md; //答案过大取模
return ans;
}
};
复杂度分析
- 时间复杂度,三层枚举循环,为 O ( n 3 ) O(n^3) O(n3)
- 空间复杂度,定义了变量 a n s ans ans,为 O ( 1 ) O(1) O(1)
算法二:动态规划
算法思路
- 通过上面的暴力做法,我们不难发现
- “520”序列的前提是“52”序列,“52”序列的前提是“5”序列
- 所以我们不难设计出状态
- f [ i ] [ 0 ] f[i][0] f[i][0]代表前 i i i个字符中,所有可能的字符串中,位置不同的为 “5” 的子序列的个数
- f [ i ] [ 1 ] f[i][1] f[i][1]代表前 i i i个字符中,所有可能的字符串中,位置不同的为 “52” 的子序列的个数
- f [ i ] [ 2 ] f[i][2] f[i][2]代表前 i i i个字符中,所有可能的字符串中,位置不同的为 “520” 的子序列的个数
- 如果当前位置字符 s [ i ] = = ′ 5 ′ s[i]=='5' s[i]==′5′,则说明“5”的子序列个数增加了一个, f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] + 1 f[i][0]=f[i-1][0] + 1 f[i][0]=f[i−1][0]+1
- 如果当前位置字符 s [ i ] = = ′ 2 ′ s[i]=='2' s[i]==′2′,则可以将之前的 f [ i − 1 ] [ 0 ] f[i-1][0] f[i−1][0]个“5”序列全部变成“52”序列,再加上之前有的,总共有 f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] f[i][1]=f[i-1][0] + f[i-1][1] f[i][1]=f[i−1][0]+f[i−1][1]
- 如果当前位置字符 s [ i ] = = ′ 2 ′ s[i]=='2' s[i]==′2′,则可以将之前的 f [ i − 1 ] [ 1 ] f[i-1][1] f[i−1][1]个“52”序列全部变成“520”序列,再加上之前有的,总共有 f [ i ] [ 2 ] = f [ i − 1 ] [ 1 ] + f [ i − 1 ] [ 2 ] f[i][2]=f[i-1][1] + f[i-1][2] f[i][2]=f[i−1][1]+f[i−1][2]
- 如果当前位置字符 s [ i ] = = ′ ? ′ s[i]=='?' s[i]==′?′,则说明它可以被替换成上面三种情况,将上面三种的转移方程照抄即可。
空间优化
- 不难发现,当前位置的状态只跟前一个位置有关
- 所以,对于一种序列的维护,我们只需要用一个变量即可
- 将空间复杂度从 O ( n ) O(n) O(n)优化到了 O ( 1 ) O(1) O(1)
- 此时注意
s
[
i
]
=
=
′
?
′
s[i]=='?'
s[i]==′?′处的转移,设之前的
f
[
i
]
[
0
]
,
f
[
i
]
[
1
]
,
f
[
i
]
[
2
]
f[i][0],f[i][1],f[i][2]
f[i][0],f[i][1],f[i][2]分别对应
a
,
b
,
c
a,b,c
a,b,c,则正确的转移顺序应该为
c
,
b
,
a
c,b,a
c,b,a
- 如果转移顺序为
a
,
b
,
c
a,b,c
a,b,c,则会导致以下的后果,从而导致错误的结果
代码实现
typedef long long LL;
class Solution
{
public:
LL a, b, c, md = 998244353;
int findOccurrences(string s)
{
a = b = c = 0; //“5”,“52”,“520”序列的方案数量
int len = s.size(); //字符串长度
s = ' ' + s; //加一,省去判断s[0]的情况,让编号从1开始
//方便编码
for (int i = 1; i <= len; i++)
{
if (s[i] == '5') //子序列为“5”的状态转移
a++;
else if (s[i] == '2') //子序列为“52”的
b += a;
else if (s[i] == '0') //子序列为“520”的
c += b;
else if (s[i] == '?') //可以转移到上面三种状态
{
c += b; //此处三行不能互相调换
b += a; //因为三种情况都是从前一个位置转移过来
//如果a或b先转移,就得到了当前位置的a或b,那么b或c的方程就会出现问题
a++;
}
a %= md, b %= md, c %= md; //答案过大取模
}
return c; //返回的即"520"序列的数目
}
};
复杂度分析
- 时间复杂度,状态转移方程递推为 O ( n ) O(n) O(n)
- 空间复杂度,定义了变量, a , b , c a,b,c a,b,c为 O ( 1 ) O(1) O(1)