问题描述
小Y有一个数字串,她希望通过分隔这个字符串来获得一些子串,每个子串代表一个数字。她的目标是最大化能获得的是 3 的倍数的数字的数量。分隔后的数字串不能包含前导零(但数字 0 本身是允许的),因为 0 也被视为 3 的倍数。
例如,对于数字串
1123,可以将其分割为[1, 12, 3],其中12和3是 3 的倍数,因此小Y最多可以获得 2 个是 3 的倍数的数字。
测试样例
样例1:
输入:
n = "1123"输出:2样例2:
输入:
n = "300"输出:3样例3:
输入:
n = "987654321"输出:6
解题思路
-
设表示在第个字符时能得到的最多3倍数数字
-
先考虑划分型DP做法:
- 当前,那么
- 反之,我们往前求和, 下标,当时,
- 最后就是答案,复杂度
-
int solution(const string& n) { //划分DP int m = n.size(); vector<int>f(m+1); for(int i = 1; i <= m; i++) { f[i] = f[i-1]; int s = 0; for(int j = i-1; j >= 0; j--) { s+= n[j] - '0'; if(s % 3 == 0)f[i] = max(f[i],f[j]+1); } } return f[m]; }
由于marscode测试点数据不大,可以通过,但建议学习的做法
-
优化
-
划分DP的做法慢是因为要往前计算求和,只要能够在的时间内计算, 那么复杂度就降为
-
如果当前求和的值,那么如果少一个,当前就能多贡献出一个答案
-
举个例子:
- 我们从开始往后求和,当到达时, 之前求和,累加当前值后
- 我们如果移除掉一个,下标为,那么此时就是一个满足答案的情况
- 我们设数组,用来记录累加值为的最新下标
- 此时多了一个1,我们就从转移过来,加上区间贡献的1,即
-
结合代码更容易理解
-
int solution(const string& n) {
int m = n.size();
vector<int>f(m+1);
vector<int>idx(3,-1);
idx[0] = 0;
int v = 0;
for(int i = 1; i <= m; i++) {
f[i] = f[i-1];
//累加值 % 3, 如果少一个 v %3 就是满足答案的
v = (v + n[i-1] - '0')%3;
//如果当前n[i] 就是3的倍数,那么答案直接+1, f[i] = f[i-1] +1
if((n[i-1]-'0')%3 == 0) f[i]++;
//如果前面有累加值为v的,那么我们以那个点为结束点j,(j,i]就是3的倍数,我们加上f[j]的答案数取最大
else if (idx[v] != -1)f[i] = max(f[i],f[idx[v]]+1);
//更新, 因为此时是值为 v 的最大值,这样才符合转移方程的定义, f[i]表示在第i个字符时能得到的最多3倍数数字
idx[v] = i;
}
return f[m];
}