一条包含字母 A-Z 的消息经过如下映射进行了 编码 :算法
'A' -> 1
'B' -> 2
...
'Z' -> 26
复制代码
要 解码 已编码的消息,全部数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 能够映射为:markdown
"AAJF" ,将消息分组为 (1 1 10 6)
"KJF" ,将消息分组为 (11 10 6)
复制代码
注意,消息不能分组为 (1 11 06) ,由于 "06" 不能映射为 "F" ,这是因为 "6" 和 "06" 在映射中并不等价。oop
给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。编码
题目数据保证答案确定是一个 32 位 的整数。spa
动态规划只能应用于有最优 子结构
的问题。最优子结构的意思是局部最优解能决定全局最优解
(对有些问题这个要求并不能彻底知足,故有时须要引入必定的近似)。code
简单地说,问题可以分解成子问题来解决
。orm
通俗一点来说,动态规划和其它遍历算法(如深/广度优先搜索)都是将原问题拆成多个子问题而后求解
,他们之间最本质的区别是,动态规划保存子问题的解,避免重复计算
。leetcode
解决动态规划问题的关键是找到状态转移方程
,这样咱们能够通计算和储存子问题的解来求解最终问题
。字符串
同时,咱们也能够对动态规划进行空间压缩
,起到节省空间消耗的效果。get
在一些状况下,动态规划能够当作是带有状态记录(memoization)的优先搜索
。
动态规划是自下而上的
,即先解决子问题,再解决父问题;
而用带有状态记录的优先搜索
是自上而下
的,即从父问题搜索到子问题,若重复搜索到同一个子问题则进行状态记录,防止重复计算。
若是题目需求的是最终状态,那么使用动态搜索比较方便;
若是题目须要输出全部的路径,那么使用带有状态记录的优先搜索会比较方便。
设 f[i] 表示字符串 s 的前 i 个字符解码方法数。在进行状态转移时 有下面的两种状况:
第一种状况是咱们使用了一个字符,即 s[i] 进行解码,那么只要 s[i] !== '0', 它就能够被解码成A∼I 中的某个字母。因为剩余的前 i−1 个字符的解码方法数为 f_i = f_i-1
状态转移方程:
f_i = f_(i-1), 其中 s[i] !== '0'
第二种状况是咱们使用了两个字符,即 s[i−1] 和 s[i] 进行编码。与第一种状况相似,s[i-1]s[i−1] 不能等于 '0',而且 s[i−1] 和 s[i] 组成的整数必须小于等于 26,这样它们就能够被解码成J∼Z 中的某个字母。因为剩余的前i−2 个字符的解码方法数为 f_(i-2) 状态转移方程:
f_i = f_(i-2), 其中 s[i-1] !== '0' 而且10 * s[i-1]+s[i] <= 26
须要注意的是,只有当i>1 时才能进行转移,不然 s[i−1] 不存在。
将上面的两种状态转移方程在对应的条件知足时进行累加获得最终结果;
/**
* @param {string} s
* @return {number}
*/
var numDecodings = function(s) {
let n = s.length;
if(n === 0) return 0;
if(!(s[0] - '0')) return 0; // 前置0的干掉
if(n === 1) return 1;
const dp = new Array(n+1).fill(0);
dp[0] = 1;
for(let i = 1; i <= n; i ++) {
if (s[i - 1] !== '0') {
dp[i] += dp[i - 1];
}
if (i > 1) {
if(s[i - 2] != '0' && ((s[i - 2] - '0') * 10 + (s[i - 1] - '0') <= 26)) {
dp[i] += dp[i - 2];
}
if((s[i-2] - '0' > 2 || s[i-2] === '') && s[i-1] === '0') return 0; // 中间不符合字母的数字干掉
}
}
return dp[n];
};
复制代码