NC601寻找520(DP)

这篇博客探讨了一种字符串问题,给定一个包含'5'、'2'、'0'和'?'的字符串,求所有可能的子序列中,位置不同的为'520'的子序列数量。文中详细介绍了两种解决方案:朴素的暴力枚举法,时间复杂度为O(n^3),以及优化后的动态规划法,时间复杂度为O(n)。动态规划法通过维护'5'、'52'、'520'序列的状态,实现了空间复杂度的优化,达到O(1)。
摘要由CSDN通过智能技术生成

题目陈述

大意:给定一个字符串仅由’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[i1][0]+1
  • 如果当前位置字符 s [ i ] = = ′ 2 ′ s[i]=='2' s[i]==2,则可以将之前的 f [ i − 1 ] [ 0 ] f[i-1][0] f[i1][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[i1][0]+f[i1][1]
  • 如果当前位置字符 s [ i ] = = ′ 2 ′ s[i]=='2' s[i]==2,则可以将之前的 f [ i − 1 ] [ 1 ] f[i-1][1] f[i1][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[i1][1]+f[i1][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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值