一條包含字母 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];
};
複製代碼