zoukankan      html  css  js  c++  java
  • 动态规划

    动态规划题目特点

    1.计数

    有多少种方式走到右下角

    有多少种方法选出K个数使得和是sum

    2.求最大最小值

    从左上角到右下角路径的最大数字和

    最长上升子序列长度

    3.求存在性

    取石子游戏,先手是否取胜

    能不能选出k个数使得和是sum

    例题:

    你有三种硬币,面值分别是2元、5元和7元,每种硬币都足够多。

    买一本书要27元,如何用最少的硬币组合正好付清,不需要对方找钱。

    动态规划组成部分一:确定状态

    状态在动态规划中的作用属于定海神针

    简单的说,解动态规划的时候需要开一个数组,数组的每一个元素f[i]或者f[i][j]代表什么。这类似于接数学题中,X,Y,Z代表什么。

    由于刚开始拿到题目时,我们往往不知道这些数组中的元素代表什么,所以就要确定状态。

    确定状态需要两个意识:
    -最后一步

    -子问题

    最后一步:我们肯定知道我们的目标是什么,我们把实现我们的目标的策略称为最优策略。而最后一步就是我们最优策略中的最后一步决策。

    在本题中,我们的目标是求一种硬币数量最少的硬币组合。我们把这种硬币组合称为最优策略。我们不知道最优策略,也就是这个组合是什么样子的,但是最优策略一定是K枚硬币a1, a2, ....., ak面值加起来是27。最后一步就是最后的一枚硬币ak.

    那么除去最后最后一步硬币ak,前面的硬币面值加起来就是27-ak。

    关键点1

    我们不关心前面的k-1枚硬币是怎么拼出27-ak的(可能有1种拼法,也可能有100种拼法),而且现在我们甚至还不知道ak和k,但是我们确定前面的硬币拼出了27-ak。

    关键点2

    因为这个是最优策略,所以拼出27-ak的硬币数也一定要最少,否则就不是最优策略了。

    可以用反证法来证明。设现在拼出27-ak的硬币数为a。如果存在一种硬币组合方式使得拼出27-ak的硬币数更少,设硬币数为b。所以由b < a。则可得,我们的最优策略的总的硬币数为a+1,前面假设的组合硬币数为b+1。显然可得 a+1 >b + 1。这就与我们的最有策略相矛盾。所以拼出27-ak的硬币数也一定是最少的。否则就不再是最优策略。

    也就是,最优策略中把最后一步去掉之后拼出27-ak的组合仍然是最优的。

    子问题

    所以我们就要求:最少用多少枚硬币可以拼出27-ak

    原问题是最少用多少枚硬币拼出27,现在我们将原问题转换成了一个子问题,而且规模更小:27-ak。

    为了简化定义,我们设状态f(x)=最少用多少枚硬币可以拼出X。

    那么我们原来是要求f(27),现在则是转换成为了f(27-ak)。

    但是现在我们还不知道最后那枚硬币ak是多少,但是最后最后那枚硬币只可能是2、5或7.

    如果ak是2, 那么f(27)应该是f(27-2) +1

    如果ak是5, 那么f(27)应该是f(27-5) +1

    如果ak是7, 那么f(27)应该是f(27-7) +1

    除此之外没有其他的可能了

    需要求最少的硬币数。所以

    使用递归方式来求解此问题,程序如下

    //递归解法
    //f(x)是最少用多少枚硬币拼出x
    //返回值res就是拼出x用的最少硬币数
    int f(int x){
        //0元钱只要0枚硬币
        if (x == 0)
            return 0;
        //把结果res初始化为无穷大。发现有比res小的值则进行进一步更新
        int res = MAX_VALUE;
        
        //如果要拼凑出3块钱,也就是f(3),我们肯定不会使用5块钱来拼凑,只会用小于3块钱的来拼凑
        //注意,这里的if语句是平行关系,并不会上面的满足条件后就跳过后面的if语句。
        //而是从上往下依次执行.当x>=7时,实现res = min(f(x - 2) + 1, f(x - 5) + 1, f(x - 7) + 1, res)
    
        //最后一枚硬币是2元
        if(x >= 2)
            res =  Math.min(f(x - 2) + 1, res);
        //最后一枚硬币是5元
        if(x >= 5)
            res =  Math.min(f(x - 5) + 1, res);
        //最后一枚硬币是7元
        if(x >= 7)
            res =  Math.min(f(x - 7) + 1, res);
        return res;
        
    }

    递归解法中的问题

    我们在求解f(27)时,程序要去递归f(25),f(22),f(20)。然后再进一步求解f(25)时就会进一步递归求解,从而形成下面的树

    我们会发现,出现大量的重复计算。如f(20)出现了3次,这里不是仅仅f(20)出现了3次,而是计算f(20)下面的递归,就是以f(20)为根节点的整棵树都计算了3次。当递归数值较大的时候计算量就是天文数字。所以递归解法做了很多的重复计算,使得效率低下。

    那么动态规划是如何来实现呢?它是通过将计算结果保存下来,并改变计算顺序来实现的。

    动态规划组成部分二:转移方程


    设状态f[x] = 最少用多少枚硬币拼出x

    那么对于任意x, 均有

    f[x] = min{f[x - 2] + 1, f[x - 5] + 1, f[x - 7] + 1}

     

    动态规划组成部分三:初始条件和边界情况

    f[x] = min{f[x - 2] + 1, f[x - 5] + 1, f[x - 7] + 1}

    两个问题:

    一:x - 2, x - 5, 或者x - 7小于0怎么办?

    二:什么时候停下来?

    如果不能拼出Y,就定义f[Y] = 正无穷

    例如f[-1] = f[-2] = ... = 正无穷

    对于1块钱,我们也是无法拼出来的,所以f[1] = min{f[-1] + 1, f[-4] + 1, f[-6] + 1} = 正无穷,表示拼不出来1。

    初始条件:f[0] = 0。0块钱用0个硬币来拼凑。这是一个很经常用的初始条件

    初始条件其实是用状态方程算不出来,但是需要的条件,这就需要手工来定义。

    动态规划组成部分四:计算顺序

    拼出x所需要的最少硬币数:f[x] = min{f[x - 2] + 1, f[x - 5] + 1, f[x - 7] + 1}

    初始条件:f[0] = 0

    然后计算f[1], f[2], ..., f[27]

    大部分动态规划的计算顺序都是从小到大,二维的计算则是从上到下,从左到右

    我们计算顺序的确定只有一个原则,就是当计算方程左边的f[x]时,方程右边的式子中的整式都已经计算过了,或者说都是已知量了。在本题中,我们计算式f[x] = min{f[x - 2] + 1, f[x - 5] + 1, f[x - 7] + 1},要计算方程左边的f[x], 等式右边的f[x - 2] , f[x - 5] , f[x - 7] 都已经有计算结果了。所以计算顺序是从小到大。

    则本题的计算顺序为

    计算f[1], f[1]无法拼凑出来

    计算f[2]

    f[3]使用了f[1], f[-2], f[-4]拼不出来

    f[4]使用了f[2], f[-1], f[-3],通过两个f[2]拼凑出来

    最后计算出f[27]

    每一步都尝试计算三种硬币,一共27步(计算f(0), f(1), ....., f(27))。

    与递归算法相比,没有任何重复计算

    算法时间复杂度(即需要进行的步数):27 * 3

    小结

    求最值型动态规划

    动态规划组成部分

      1.确定状态

        最后一步(最优策略中使用的最后一枚硬币ak)

        化成子问题(最少的硬币拼出更小的面值27 - ak)

      2.转移方程

        f[x] = min{f[x - 2] + 1, f[x - 5] + 1, f[x - 7] + 1}

      3.初始条件和边界情况

        f[0] = 0,如果不能拼出Y,则f[Y] = 正无穷

        初始条件其实是用状态方程算不出来,但是需要的条件,这就需要手工来定义。并且意义很清晰,能够明确得出

      4.计算顺序

        我们计算顺序的确定只有一个原则,就是当计算方程左边的f[x]时,方程右边的式子中的整式都已经计算过了,或者说都是已知量了。

        大部分都是从小到大计算,在二维中则多是从上到下,从左到右计算。

    消除冗余,加速计算。

    本题代码

    public class Solution{
        
        public: 
        //硬币放在数组A里面,
        //M代表要拼出的钱数
            int coinchange(int A[], int M){
                //在c++语言中,数组下标从0开始,我们要计算到M,那么开辟的内存空间大小应该是M+1
                int f[M + 1];
                //硬币种类的数量
                int n = sizeof(A) / sizeof(int); 
                
                //定义一个非常大的不可能的数当作无穷大
                very_large = 100000;
                
                //initialization
                //指定初始条件
                f[0] = 0;
                
                int i, j;
                //f[1], f[2], ... , f[27]
                for(i = 1; i <= M;++i)
                {
                    //把初始值设置为无穷大,有情况比它好就去更改
                    f[i] = very_large;
                    //这里是最后一步。也就是我们要拼出i块钱,最后一枚硬币A[j]的值
                    //f[i] = min(f[i - A[0]] + 1, ..., f[i - A[n - 1]] + 1)
                    for(j = 0; j < n; j++)
                    {
                        //由于i - A[j]可能是负数
                        //在数学上,无穷大加一还是无穷大,但是在编程中,则就会出现溢出问题
                        //所以如果是其它语言,在if语句内还要加个判断f[i - A[j]] != Interger.MAX_VALUE
                        if(i >= A[j])
                        {
                        f[i] = min(f[i - A[j]] + 1, f[i]);
                        }
                    }
                    
                    
                }
                
                if(f[M] >= 99000)
                    f[M] = -1;
                
                return f[M]            
            }
    }

    (二)

    题意:

    给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者向右走一步,问有多少种不同的方式走到右下角。

    注意:不管什么样的动态规划都可以使用前面介绍的四个部分来求解。

    动态规划组成部分一:确定状态

    最后一步:无论机器人用何种方式到大右下角,总有最后挪动的一步:向下 或者向右。

    右下角坐标设为(m - 1, n - 1)

    那么前一步机器人一定是在(m - 2, n - 1)或者在(m - 1, n - 2)

    那么,如果机器人右x中方式从左上角走到(m - 2, n - 1),有y种方式走到(m - 1, n - 2), 则机器人有x + y 种方式走到(m - 1, n - 1).

    这里为什么是x + y呢?就是加法原理得到的。

    问题转化为:机器人有多少种方式从左上角走到(m - 2, n - 1)和(m - 1, n - 2)

    原题要求有多少种方式从左上角走到(m - 1, n - 1)

    子问题

    状态:设f[i][j]为机器人有多少种方式从左上角走到(i,j).。数组下标[i][j]就是网格(i,j)

    注:至于数组为几维,则应按照原问题种有几个变量来设置。原问题中有几个变量,则设置为几维。在本题中有m-1, n-1两个变量,则开二维数组。

    动态规划组成部分二:转移方程

     动态规划组成部分三:初始条件和边界情况

    初始条件:f[0][0] = 1, 因为机器人只有一种方式到左上角。

    边界情况:i = 0或j = 0时,则前一步只能由一个方向过来,所以f[i][j] = 1

    动态规划组成部分四:计算顺序

    f[0][0] = 1

    计算第0行: f[0][0], f[0][1], ... , f[0][n-1]

    计算第1行: f[1][0], f[1][1], ... , f[1][n-1]

    .....

    计算第m - 1行: f[m - 1][0], f[m - 1][1], ... , f[m - 1][n-1]

    这样的计算顺序是为了保证在计算(i,j)格子时,前面的问题都已经计算过。

    答案是f[m - 1][n - 1]

    时间复杂度(计算步数):O(MN),空间复杂度(数组大小):O(MN)

    本题代码

    class Solution{
        public:
            int uniquePaths(int m, int n){
                int f[][] = new int[m][n];
                int i, j;
                for(i = 0; i < m; i++){ //row: up to down
                    for(j = 0; j < n; j++){ //column: left to right
                        if(i == 0 || j == 0)
                        {
                            //这里也包含了初始条件f[0][0] = 1
                            f[i][j] = 1;
                        }
                        else
                        {
                            f[i][j] = f[i - 1][j] + f[i][j - 1];
                        }
                    }
                }
                return f[m - 1][n - 1];
            }
    }

     (三):存在型动态规划

    有n块石头分别摆在x轴的0, 1, ... , n-1位置,如果一只青蛙在石头0,想要跳到石头n-1, 如果青蛙在第i块石头,它最多可以向右跳跃距离ai

    问题:青蛙能否跳到石头n - 1

    例子:输入:a = [2, 3, 1, 1, 4] 输出:True         输入:a = [3, 2, 1, 0, 4]    输出:False

    组成部分一:确定状态

    最后一步:如果青蛙能够到最后一块石头n-1, 我们考虑他的最后一步

    这一步时从石头i跳过来, i < n - 1

    这需要同时满足两个条件:

      青蛙可以跳到石头i

      最后一步不超过跳跃的最大距离: n - 1 - i <= ai

    两个条件中第二个条件很容易判断。那么剩下的就是第一个条件:我们需要知道青蛙能不能跳到石头i(i < n-1)

    而我们原来要求青蛙能不能跳到石头n - 1。这就成为一个子问题

    状态:设f[j]表示青蛙能不能跳到石头j

    组成部分二:转移方程

    设f[j]表示青蛙能不能跳到石头j

    f[j] = ORo<=i<j(f[i] AND i + a[i] > = j)

     

    式中:OR表示进行for循环的时候只要有一个满足,就输出True,否则就输出False

    组成部分三:初始条件和边界情况

    设f[j]表示青蛙能不能跳到石头j

    初始条件: f[0] = True, 因为青蛙一开始就在石头0

    没有边界情况

    计算顺序

    设f[j]表示青蛙能不能跳到石头j

    f[j] = ORo<=i<j(f[i] AND i + a[i] > = j)

    初始化f[0] = True

    计算f[1], f[2], ... , f[n - 1]

    答案是f[n - 1]

    class Solution{
        public:
            boolean canjump(int A[]){
                int n = sizeof(A) / sizeof(int);
                bool f[] = new bool[n];
                f[0] = true;
                for(int j = 1; j < n; j++)
                {
                    f[j] = false;
                    //previous stone i
                    //last jump is from i to j
                    for(int i = 0; i < j; i++)
                    {
                        if(f[i] && i + A[i] >= j)
                        {
                            f[j] = true;
                            break;
                        }
                    }
                }
                return f[n - 1]
            }
    }

    (三)

    给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者想右走一步

    网格中有一些障碍,机器人不能通过障碍

    问有多少种不同的方式走到右下角

    组成部分一:确定状态

    机器人最后到达右下角(m - 1, n - 1)处,那么上个位置一定是在(m - 2, n - 1)或者(m - 1, n - 2)处。那么机器人到达右下角(m - 1, n - 1)的方式数量为到达(m - 2, n - 1)方式数量和到达(m - 1, n - 2)的方式数量之和。

    原问题为:有多少种不同方式到达右下角

    子问题为:有多少种方式到达位置(m - 2, n - 1)和位置(m - 1, n - 2)

    组成部分二:状态方程

    设f[i][j]为走到网格[i][j]处的方式数目。那么有f(m-1)(n-1)为到达位置(m -  1, n - 1)处的方式数量,那么到达(m - 2, n - 1)和(m - 1, n - 2)处的方式数量分别为f(m - 2)(n - 1)和f(m - 1)( n -2)

    则状态方程为

    f(i)(j) = f(i-1)(j) + f(i)(j-1)

    组成部分三:初始条件和边界情况

    当机器人位于点(0, 0 )处时,方式数数目为1。也就是机器人只有一种方式到达点(0,0),即f(0)(0) = 1

    如果左上角和右下角有障碍,那么机器人永远无法到达,直接输出零

    如果(i, j)处右障碍,则f[i][j] = 0,表示机器人无法到达此网格。

    那么转移方程为

     

    组成部分四:计算顺序

    计算顺序按照从上到下,从左到右的顺序计算进行

    class Solution{
        
        public:
            int uniquepathswithobstacles(int A[][])
            {
                if(A == NULL)
                {
                    return 0;
                }
                int m = sizeof(A) / sizeof(A[0]);
                int n = sizeof(A[0]) / sizeof(A[0][0]);
                
                int f[][] = new int[m][n];
                //做一个数组,下标为[i][j]的数组就是到达网格(i,j)处的方式数量。
                //而且按照从上到下、从左到右的顺序计算,在进行计算(i,j)处的值时,
                //前j - 1行中的值全部计算完成,并且第j行前i-1个元素也已经计算完成。
                //在计算第(i, j)处的值时可通过下标来使用计算过的值
                
                for(int i = 0; i < m; i++)                //行从左到右
                {
                    for(int j = 0; j < n; j++)  //列从上到下
                    {
                        //一票否决
                        //如果A[i][j] = 1表示此处有障碍,将f[i][j]置为0,
                        //同时使用continue语句进行下轮循环进行下一个网格的计算
                        if(A[i][j] = 1)
                        {
                            f[i][j] = 0;
                            continue;
                        }
                        
                        if( i == 0 && j == 0)
                        {
                            f[i][j] = 0;
                            continue;
                        }
                        
                        //计算f[i][j]时先将f[i][j]置为0,然后再更新
                        f[i][j] = 0;
                        
                        //如果j=0,表示在第1列,不执行此语句。此时f[i][j] = 0 + f[i - 1][j]
                        //如果i=0,表示在第1行,不执行下一个if语句,此时f[i][j] = f[i][j - 1]
                        //如果i,j都大于0,则两个if语句同时执行,有f[i][j] = f[i][j - 1] + f[i - 1][j]
                        if( j >0)
                        {
                            f[i][j] += f[i][j - 1];
                        }
                        
                        if( i > 0)
                        {
                            f[i][j] += f[i - 1][j];
                        }
                        
                    }
                }
                return f[m - 1][n - 1]
            }
    }

    序列型规划

    题目:

    有一排N栋房子,每栋房子要漆成三种颜色中的一种:红、蓝、绿

    任何两栋相邻的房子不能漆成同样的颜色

    第i栋房子漆成红色、蓝色、绿色的花费分别时cost[i][0], cost[i][1], cost[i][2]

    问最少需要花多少钱漆完这些房子

    例子:

    输入:

    N = 3

    cost = [[14, 2, 11], [11, 14, 5], [14, 3, 10]]

    输出

    -10 (第0栋房子蓝色,第1栋房子绿色,第2栋房子蓝色。 2 + 5 + 3 = 10)

    为了便于理解,首先统一一下编号问题。

    前N栋房子,是指房子共有N栋。是生活中习惯用语,前1栋就只有一栋,前2栋就是两栋房子。

    而在java和c++中,数组下标是从0开始,所以房子N-1指的是第N栋房子(前N栋房子的最后一栋)。

    综上所述:前N栋房子并且房子N-1是红色、蓝色、绿色的最小花费是指把前N栋房子都油漆完并且将第N栋(前N栋中的最后一栋)漆成红色、蓝色、绿色的花费。

    组成部分一:确定状态

    最优策略时花费最小的策略

    在这里,第一栋房子编号为0,所以,前N栋房子的最后一栋房子编号为N-1。

    最后一步:最优策略中房子N-1一定染成了红、蓝、绿中的一种。

    但是相邻两栋房子不能漆成一种颜色

    所以如果最优策略中房子N-1是红色,房子N-2只能是蓝色或绿色

    所以如果最优策略中房子N-1是蓝色,房子N-2只能是红色或绿色

    所以如果最优策略中房子N-1是绿色,房子N-2只能是蓝色或红色

    如果直接套用以前的思路,记录油漆前N栋房子的最小花费

    根据套路,也需要记录油漆前N-1栋房子的最小花费。

    但是,前N-1栋房子的最小花费的最优策略中,不知道房子N-2(所有房子的倒数第二栋房子)是什么颜色,所以房子N-2有可能与房子N-1撞色。

    为解决这个问题,所以有

    不知道房子N-2是什么颜色,就把它记录下来。就是在状态里面增加一个维度,用来记录房子N-2的颜色。

    分别记录油漆前N-1栋房子并且房子N-2(倒数第二栋)是红色、蓝色、绿色的最小花费。这个过程中,我们需要记录三个数值。就是我们记录油漆前N-1栋房子并且N-2栋房子是红色的最小花费、记录油漆前N-1栋房子并且N-2栋房子是蓝色的最小花费、记录油漆前N-1栋房子并且N-2栋房子是绿色的最小花费。

    如果N-1栋房子是红色,那么在N-2栋房子我们只需看蓝色和绿色

    如果N-1栋房子是蓝色,那么在N-2栋房子我们只需看红色和绿色

    如果N-1栋房子是绿色,那么在N-2栋房子我们只需看红色和蓝色

    原问题:

    求油漆前N栋房子且房子N-1(最后一栋)是红色、蓝色、绿色的最小花费。

    子问题:

    需要知道求油漆前N-1栋房子且房子N-2(倒数第二栋)是红色、蓝色、绿色的最小花费。

    状态:

    设油漆前i栋房子并且房子i-1是红色、蓝色、绿色的最小花费分别为f[i][0]、f[i][1]、f[i][2]

    组成部分二:转移方程

    设油漆前i栋房子并且房子i-1是红色、蓝色、绿色的最小花费分别为f[i][0]、f[i][1]、f[i][2]

    以第一个式子为例来理解这个等式

    下标的第二个维度是颜色的标记,0,1,2分别代表三种颜色。

    f[i][0]表示油漆前i栋房子,并且i-1(前i栋房子的最后一栋)房子是颜色0的最小花费。

    在这里,如果不考虑颜色冲突问题,那么油漆前i栋房子的费用等于油漆前i-1栋房子的费用加上油漆房子i-1的费用。

    f[i-1][1]表示油漆前i-1栋房子,并且将房子i-2油漆成颜色1的最小花费

    cost[i-1][0]:表示将房子i-1油漆成颜色0的费用。

    整个式子的意思为:

    油漆前i栋房子并且房子i-1油漆成颜色0的最小费用 = 油漆前i-1栋房子并且房子i-2油漆成颜色1的费用 + 将房子i-1油漆成颜色0的费用。

    组成部分三:初始条件和边界情况

    设油漆前i栋房子并且房子i-1是红色、蓝色、绿色的最小花费分别为f[i][0]、f[i][1]、f[i][2]

    初始条件:f[0][0] = f[0][1] = f[0][2] = 0。即不油漆任何房子的花费。

    无边界情况

    组成部分四:计算顺序

    设油漆前i栋房子并且房子i-1是红色、蓝色、绿色的最小花费分别为f[i][0]、f[i][1]、f[i][2]

    初始化f[0][0]、f[0][1]、f[0][2]

    计算f[1][0]、f[1][1]、f[1][2]

    .。。。。

    计算f[N][0]、f[N][1]、f[N][2]

    答案是min{f[N][0]、f[N][1]、f[N][2]}, 时间复杂度O(N), 空间复杂度O(N)

    在下面代码中设计最小值函数在运行时需要进行重新书写

    class solution{
        public:
            int minCost(int C[][])
            {
                int n = sizeof(C) / sizeof(C[0]);
                if(n == 0)
                {
                    return 0;
                }
                
            //这里一定要开n+1个空间
            //因为除了存储1,... , N栋房子的花费,还要存储0栋房子时的花费
            int f[][] = new int[n + 1][3];    
            
            //初始化,油漆0栋房子的最小花费
            f[0][0] = 0;
            f[0][1] = 0;
            f[0][2] = 0;
            
            for(int i = 1; i <= n; i++)
            {
                //前i栋房子,最后一栋房子为i
                //第i栋房子要染成j颜色
           //这里计算的是将第i栋房子漆成颜色j的最小费用
    for(int j = 0; j < 3; j++) { f[i][j] = 100000; //第i-1栋房子要染成K颜色 //这里的计算顺序是从前向后计算的 //在计算f[i][j]时,前面的元素都已经计算完全。 //所以这里f[i - 1][k]是已知量,是第i-1栋房子被染成颜色k的最小费用。用这个已知量来求f[i][j] //将这个循环套在j的循环之内,是因为求第i栋房子漆成颜色j时的费用,要求i-1的颜色k不能等于j // for(int k = 0; k < 3; k++) { if(j != k ) { f[i][j] = min(f[i][j], f[i - 1][k] + C[i - 1][j]); } } } } return min(f[n][0], f[n][1], f[n][2]); } }

    序列型动态规划: ...前i个...最小/方式数/可行性

    在设计动态规划过程中,发现需要知道油漆前N-1栋房子的最有策略中,房子N-2的颜色

    如果只用f[N-1],将无法区分

    解决方法:记录下房子N-2的颜色

     - 在房子N-2是红/蓝/绿的情况下,油漆N-1栋房子的最小花费

    序列 + 状态

    划分型动态规划

    题目:有一段由A-Z组成的字母串信息被加密成数字串

    加密方式:A->1, B-> 2, ... , Z-> 26

    给定加密后的数字串S[0 ... N-1], 问有所少种方式解密成字母串

    例子:

    输入:12          输出:2(AB或L)

     组成部分一:确定状态

    解密数字串,即划分成若干段数字,每段数字对应一个字母

    最后一步:对应一个字母。(在划分型问题中,最后一步对应的是最后一段。无论怎么划分,最后一段数字一定对应一个字母)

    -A, B, ... , Z

    这个字母加密时对应的数字串为:1, 2, ... , 26

    假设这是具有N个数字的数字串。

    那么最后一个字母只能对应两个数字或者一个数字,除此之外没有其它的对应关系。

    如果最后一个字母对应一位数字,那么在这个问题中就是2.那么字母就是B。假设前N-1个字母共有100种解密方式

    如果最后一个字母对应两位数字,那么在这个问题中就是数字12,对应的字母就是L。假设前N-2个字母共有50种解密方式

    除此之外,最后一个字母没有其它的可能。那么

    子问题

    设数字串长度为N

    原问题:要求数字串前N个字符的解密方式数

    子问题:需要知道数字串前N-1和N-2个字符的解密方式数。

    状态:设数字串前i个数字解密成字母串有f[i]种方式。

    组成部分二:转移方程

    设数字串S前i个数字揭密乘字母串有f[i]种方式,则

    组成部分三:初始条件和边界情况

    设数字串S前i个数字揭密乘字母串有f[i]种方式

    初始条件:f[0] = 1, 即空串有1种方式解密 ,即解密成空串

    边界情况:如果i = 1,只看最后一个数字

    组成部分四:计算顺序

    f[0], f[1], ... , f[N]

  • 相关阅读:
    漫谈PID——实现与调参
    匿名上位机的使用(51版)
    #if 和 #ifdef 条件编译注意
    关于HC04超声波模块测距的思考(51版)
    关于C语言文件操作
    C语言实现简易2048小游戏
    关于OELD屏显示电池电量的简易方法
    使用notepad++作为keil的外部编辑器
    2017年个人总结
    数据存入文件
  • 原文地址:https://www.cnblogs.com/hxhlrq/p/13284220.html
Copyright © 2011-2022 走看看