题目陈述
大意:给定一个字符串仅由'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; } };
复杂度分析
- 时间复杂度,三层枚举循环,为
- 空间复杂度,定义了变量,为
算法二:动态规划
算法思路
- 通过上面的暴力做法,我们不难发现
- “520”序列的前提是“52”序列,“52”序列的前提是“5”序列
- 所以我们不难设计出状态
- 代表前个字符中,所有可能的字符串中,位置不同的为 "5" 的子序列的个数
- 代表前个字符中,所有可能的字符串中,位置不同的为 "52" 的子序列的个数
- 代表前个字符中,所有可能的字符串中,位置不同的为 "520" 的子序列的个数
- 如果当前位置字符,则说明“5”的子序列个数增加了一个,
- 如果当前位置字符,则可以将之前的个“5”序列全部变成“52”序列,再加上之前有的,总共有
- 如果当前位置字符,则可以将之前的个“52”序列全部变成“520”序列,再加上之前有的,总共有
- 如果当前位置字符,则说明它可以被替换成上面三种情况,将上面三种的转移方程照抄即可。
空间优化
- 不难发现,当前位置的状态只跟前一个位置有关
- 所以,对于一种序列的维护,我们只需要用一个变量即可
- 将空间复杂度从优化到了
- 此时注意处的转移,设之前的分别对应,则正确的转移顺序应该为
- 如果转移顺序为,则会导致以下的后果,从而导致错误的结果
代码实现
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"序列的数目 } };
复杂度分析
- 时间复杂度,状态转移方程递推为
- 空间复杂度,定义了变量,为