个人刷题笔记-leetcode91-动态规划

leetcode面试100题刷了40多道,好多题都是涉及到动态规划的,每次做完都以为自己掌握了,实际上并没有…做到第91题又是动态规划,于是总结一下,加深记忆。

动态规划

动态规划和递归很像,但是也有不同点。递归是自顶向下实现的,需要压栈;动态规划也有自顶向下的形式,但是比递归多了存储状态这一部分,避免了重复计算。一般来说动态规划是自底向上的,像迭代一样,避免了压栈过程。 好比爬楼梯,递归是从楼顶逐个往下计算,到楼底再回去;自底向上的动态规划就是直接从楼底一步步向楼顶计算。

第91题如下图:
个人刷题笔记-leetcode91-动态规划_第1张图片
每个字母和数字有映射关系,给你一个只包含数字的字符串,计算出他解码为字母共有多少种组合。其中‘0’这个数字比较特殊,需要注意。

根据阅读别的博客和自己做题总结,动态规划也可以像回溯算法一样包括以下几个部分:①设置一个数组,用来存储状态。数组大小的设置目前还很迷惑。记得初始化②找出递推关系式。这是关键。③自底向上,所以需要一个for循环。这个for循环一般根据输入的数据进行相应设计。

先找一下递推关系。涉及到字符串的递归题好像都要根据子串找出递推关系。根据https://leetcode.wang/大佬的思路, 比如我们有一个字符串232323,他的子串可以有两种形式(因为一个数字或者两个数字都可以代表一个字母)23232(3) 和 2323(23),那么这两个子串的解码数量加起来就是当前字符串的解码总数量了(类似leetcode70题走台阶,要到最后一个台阶f(n)有两种走法,从倒数第二个f(n-2)或者倒数第一个f(n-1)走上去)。

这一题,输入是个字符串,那我们就设置一个数组dp,dp的长度为字符串长度加1,dp[m]表示长度为m的子串。个人刷题笔记-leetcode91-动态规划_第2张图片
这么设置的理由目前我是这么理解的,如上图。我们的递归公式是dp[m] = dp[m-1] + dp[m-2](m=i+1),那i=1时候,23这个字符串有两种解码方式(2,3)和(23)。这样的话用dp[2] = dp[1] + dp[0]表示比较好
如此以来,初始化也就确定了,必须设置dp[0]=1, dp[1]=1。

讨论字符串带0的两种特殊情况。
①开头为0,直接返回0,无法解码。
②假设当前字符为0, 比如10,20这种字符串,他们只能拆为dp[m-2](如20),因为2(0)无法解码。 对于30这种字符串,303(0)都无法解码,直接跳过。
一般情况(当前字符不为0):
① 01 02 27 29这种字符串,dp[m]只能等于dp[m-1](如01,因为dp[m-2] 01无意义)
②11-25中间的,可以随便拆分,dp[m] = dp[m-1] + dp[m-2];

  public static int numDecodings(String s) {
        char[] all = s.toCharArray();
        int[] dp = new int[all.length + 1];
        if (all[0] == '0') return 0;//开头为0,直接返回0,无法解码。

        dp[0] = 1;//初始化
        dp[1] = 1;//初始化
        
        for (int i = 1; i < all.length; i++) {
            if (all[i] == '0') {//带0的特殊情况
                if(all[i-1]=='1' || all[i-1]=='2')
                    dp[i+1]=dp[i - 1];
                continue;
            }
            int c = (all[i - 1] - '0') * 10 + (all[i] - '0');
            if (c < 10 || c > 26) dp[i + 1] = dp[i];//一般情况
            else if (c > 10 && c <= 26) dp[i + 1] = dp[i] + dp[i - 1];
        }

        return dp[all.length];
    }

你可能感兴趣的:(个人刷题笔记-leetcode91-动态规划)