一起刷LeetCode——解码方法(动态规划)

128 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码 : 'A' -> "1" 'B' -> "2" ... 'Z' -> "26"。 给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

来源:leetcode.cn/problems/de…

分析

  • 通过题目描述,有点像爬楼梯,要么一步,要么两步,对应的是字符串1位或者2位1-26对应A-Z。
  • 对于这种确定总数的题目可以使用动态规划。

动态规划

  • 首先把整个问题分解成小问题,这个小问题即1位字符,那解码方法数要么是1要么是0(字符是‘0’的时候不能解码),解码方法的总数状态树从叶子节点向根节点叠加。
  • 确定状态:dp[i]表示s的第i个位置解码方法数
  • 初始化:当s为0的时候不能解码,其他情况下dp[s.length-1]=1
  • 状态转移:位置i的解码方法有两种,即i后1位字符(0-9)或者2位字符(10-26),即dp[i] = dp[i+1]+dp[i+2]

代码

/**
 * @param {string} s
 * @return {number}
 */
var numDecodings = function (s) {
    const dp = [];
    dp[s.length] = 1;
    for (let i = s.length - 1; i >= 0; i--) {
        if (s[i] === '0') {
            dp[i] = 0;
        }
        else {
            dp[i] = dp[i + 1]
        }
        if (i + 2 <= s.length && (s[i] === '1' || s[i] === '2' && '0123456'.includes(s[i + 1]))) {
            dp[i] += dp[i + 2]
        }
    }
    return dp[0];
};

另一种动态规划

  • 确定状态:dp[i]是s[i]为结尾的子字符串的解码方法
  • 初始化:当字符串为0的时候不能解码,其他情况下dp[0] = 1
  • 状态转移:当s[i]不是0的时候,dp[i]=dp[i-1],如果前两位能解码组成10-26,dp[i]=dp[i-1]+dp[i-2]
  • 需要注意i-1和i-2下标不溢出

代码

var numDecodings = function(s) {
    const n = s.length;
    const f = new Array(n + 1).fill(0);
    f[0] = 1;
    for (let i = 1; i <= n; ++i) {
        if (s[i - 1] !== '0') {
            f[i] += f[i - 1];
        }
        if (i > 1 && s[i - 2] != '0' && ((s[i - 2] - '0') * 10 + (s[i - 1] - '0') <= 26)) {
            f[i] += f[i - 2];
        }
    }
    return f[n];
};

总结

  • 虽然都是动态规划,但是对于状态的定义不一样,状态转移方程和最后返回的表达式也不同,边界函数也不一样,但是很多时候在解动态规划的题目的时候,都忽略了定义状态的重要性,有时候换个定义,理解起来就不一样了
  • 我们习惯根据题意从上向下画状态树,分析状态也都是从上往下,有时候从下往上反过来分析也提供了另一种思路
  • 今天也是有收获的一天