[动态规划]:348.分隔字串获取3的倍数问题

149 阅读2分钟

问题描述

小Y有一个数字串,她希望通过分隔这个字符串来获得一些子串,每个子串代表一个数字。她的目标是最大化能获得的是 3 的倍数的数字的数量。分隔后的数字串不能包含前导零(但数字 0 本身是允许的),因为 0 也被视为 3 的倍数。

例如,对于数字串 1123,可以将其分割为 [1, 12, 3],其中 123 是 3 的倍数,因此小Y最多可以获得 2 个是 3 的倍数的数字。


测试样例

样例1:

输入:n = "1123" 输出:2

样例2:

输入:n = "300" 输出:3

样例3:

输入:n = "987654321" 输出:6

解题思路

  • fif_i表示在第ii个字符时能得到的最多3倍数数字

  • 先考虑划分型DP做法:

    • 当前n[i]%3==0n[i] \% 3 == 0,那么f[i]=f[i1]+1f[i] = f[i-1] + 1
    • 反之,我们往前求和, 下标j=i1j = i - 1,当s%3==0s \%3==0时,f[i]=max(f[i],f[j]+1)f[i] = max(f[i], f[j]+1)
    • 最后f[len(n)]f[len(n)]就是答案,复杂度O(n2)O(n^2)
    • 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测试点数据不大,O(n2)O(n^2)可以通过,但建议学习O(n)O(n)的做法

  • 优化

    • 划分DP的做法慢是因为要往前计算求和,只要能够在O(1)O(1)的时间内计算, 那么复杂度就降为O(n)O(n)

    • 如果当前求和的值s%3==1s \% 3 ==1,那么如果少一个11,当前就能多贡献出一个答案

    • 举个例子: n=112n = 112

      • 我们从开始往后求和,当到达i=2,n[i]=2i = 2, n[i] =2 时, 之前求和s=2s = 2,累加当前值后s=(2+2)%3=1s = (2 + 2) \% 3 = 1
      • 我们如果移除掉一个11,下标为jj,那么此时(j,i](j,i]就是一个满足答案的情况
      • 我们设idxidx数组,idx0,1,2idx_{0,1,2}用来记录累加值ss0,1,20,1,2最新下标
      • 此时多了一个1,我们就从f[idx[1]]f[idx[1]]转移过来,加上(idx[1],i](idx[1] , i]区间贡献的1,即f[i]=max(f[i],f[idx[1]]+1)f[i] = max(f[i], f[idx[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];
}