解码方法 图解题解
这道题到底在问什么
- 输入
- s="226122618"
- 输出
- 30
最优解:为什么这么做
一句话答案:LeetCode 91 解码方法像爬楼梯的计数动态规划:dp[i] 记前 i 位的方案数,末位非 '0' 就继承 dp[i-1],末两位在 10~26 就加 dp[i-2],两来路相加。'0' 最易翻车,时间 O(n)、空间 O(1)。
解码方法这道题到底在数什么
给一串数字 s,1 对 A、2 对 B、直到 26 对 Z。切成若干段、每段是 1~26 的数字,就还原一种字母串,问能解出多少种。226 可切成 2|2|6、22|6、2|26,答案是 3。要的是方案总数,不是某一种切法,是道计数题。
为什么不能把所有切法都试一遍
递归枚举切点:当前位既能单独成段,也能和前一位凑成两位段,一路分叉。每往前一位多一次二选一,切法随长度指数膨胀,串一长数不完,病根是同一个后缀(从某位到结尾的那截)被反复重算。换个记账法:只盯「前 i 位有几种方案」,算过存下来复用,指数枚举就压成线性递推(用前面算好的值一步步推出后面的)。
dp[i] 定成前 i 位的方案数,dp[0] 为什么是 1
定义 dp[i] 为「s 前 i 位的解码方案数」,i 数的是位数、不是字符下标。dp[0] = 1 代表空串——还没解出字母,算一种空方案;它不是真有字母,而是给「末两位合并」当基准:合并时前面剩的正好是空串,靠它记上一票。dp[1] 看首位,非 '0' 就能单独成字母,dp[1] = 1。
两条来路怎么相加,'0' 又卡在哪
算 dp[i] 时,落到第 i 位的最后一步有两种来路。一是末位单独成字母,前提是 s[i] 不是 '0'(没有字母配 0),成立就继承 dp[i-1];二是末两位合并成字母,前提是这个两位数在 10~26,成立就加 dp[i-2]。两条末段长度不同,不会数重,相加即全部。
'0' 的坑全从这两个前提长出来。单独的 '0' 解不了,末位是 '0' 时第一条来路作废;两位数要不小于 10,'06' 带前导 0 不在 10~26,第二条也作废。某个 '0' 两条全断整串无解,比如 '30':0 不能单独、30 又超 26。开头 '0' 同理,第一位无字母可对,返回 0。
拿 226122618 亲手把 dp 填一遍
空串 dp[0] = 1,'2' 非 0 得 dp[1] = 1。第 2 位 '2':继承 1,末两位 22 在 10~26 加 1,dp[2] = 2。第 3 位 '6':继承 2,26 加 1,dp[3] = 3。第 4 位 '1':继承 3,61 超 26 作废,dp[4] = 3。第 5 位 '2':继承 3,12 加 3,dp[5] = 6。第 6 位 '2':继承 6,22 加 3,dp[6] = 9。第 7 位 '6':继承 9,26 加 6,dp[7] = 15。第 8 位 '1':继承 15,61 超 26 作废,dp[8] = 15。第 9 位 '8':继承 15,18 加 15,dp[9] = 30,就是整串答案。
复杂度是多少,哪些 '0' 的边界最坑
从第 2 位推到第 n 位,每位只做常数次判断,时间 O(n);dp[i] 只依赖 dp[i-1] 和 dp[i-2],两个变量轮替就够,不必存整张表,空间 O(1)。参考代码的 a、b 就是这两项。
另两个易错点:两位数要卡死 10~26 两头,'09' 小于 10 非法、'27' 大于 26 只能拆开;别把 dp[0] 当真有字母,它只是空串那一票基准。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住这条转移式:单字符一条路、两位数一条路,能走就把对应的方案数加进来。
- 4上行是字符串 s 的每一位(固定不动),下行 dp 待填。第 0 列代表「空串」,从最左边开始。
- 5约定空串算 1 种解法(dp[0]=1),它是后面「合并两位」时的起点账本,不是真有字母。
- 6只看第 1 位 '2':它是 1~9 的数字,能单独解成一个字母,所以 dp[1]=1。
- 7算 dp[2](前 2 位):末 1 位 '2' 能单独成字母,继承 dp[1]=1;末 2 位 22=22 落在 10~26,能合并成字母,再加 dp[0]=1。
- 8两条来路相加:1 + 1 = 2,填进 dp[2]。 两条路都通,方案数翻倍叠加。
- 9算 dp[3](前 3 位):末 1 位 '6' 能单独成字母,继承 dp[2]=2;末 2 位 26=26 落在 10~26,能合并成字母,再加 dp[1]=1。
- 10两条来路相加:2 + 1 = 3,填进 dp[3]。 两条路都通,方案数翻倍叠加。
- 11算 dp[4](前 4 位):末 1 位 '1' 能单独成字母,继承 dp[3]=3;末 2 位 61=61 大于 26(超出 Z),合并非法,这条路作废。
- 12两条来路相加:3 + 0 = 3,填进 dp[4]。 只有单字符这一条路。
- 13算 dp[5](前 5 位):末 1 位 '2' 能单独成字母,继承 dp[4]=3;末 2 位 12=12 落在 10~26,能合并成字母,再加 dp[3]=3。
- 14两条来路相加:3 + 3 = 6,填进 dp[5]。 两条路都通,方案数翻倍叠加。
- 15算 dp[6](前 6 位):末 1 位 '2' 能单独成字母,继承 dp[5]=6;末 2 位 22=22 落在 10~26,能合并成字母,再加 dp[4]=3。
- 16两条来路相加:6 + 3 = 9,填进 dp[6]。 两条路都通,方案数翻倍叠加。
- 17算 dp[7](前 7 位):末 1 位 '6' 能单独成字母,继承 dp[6]=9;末 2 位 26=26 落在 10~26,能合并成字母,再加 dp[5]=6。
- 18两条来路相加:9 + 6 = 15,填进 dp[7]。 两条路都通,方案数翻倍叠加。
- 19算 dp[8](前 8 位):末 1 位 '1' 能单独成字母,继承 dp[7]=15;末 2 位 61=61 大于 26(超出 Z),合并非法,这条路作废。
- 20两条来路相加:15 + 0 = 15,填进 dp[8]。 只有单字符这一条路。
- 21算 dp[9](前 9 位):末 1 位 '8' 能单独成字母,继承 dp[8]=15;末 2 位 18=18 落在 10~26,能合并成字母,再加 dp[7]=15。
- 22两条来路相加:15 + 15 = 30,填进 dp[9]。 两条路都通,方案数翻倍叠加。
- 23最右 dp[9]=30 就是整串 "226122618" 的解码方案总数。
⚠️ 容易写错的地方
✗ 错:把单独的 0 也算成一种解
✓ 对:'0' 不对应任何字母,单字符这条路必须排除
A~Z 是 1~26,没有 0
✗ 错:末 2 位带前导 0 还合并
✓ 对:如 '06' 不在 10~26,合并非法
两位数必须 ≥10
✗ 错:两位数超过 26 还合并
✓ 对:如 '27' >26,只能拆成 2 和 7
Z 最大对应 26
✗ 错:dp[0] 当成真有字母
✓ 对:dp[0]=1 只是空串的记账起点
为「末 2 位合并」提供 +1 的基准
完整代码(Python / C++ / Java)
Python
def numDecodings(s: str) -> int:
if not s or s[0] == "0": return 0
a, b = 1, 1 # dp[i-2], dp[i-1]
for i in range(1, len(s)):
cur = b if s[i] != "0" else 0
if 10 <= int(s[i-1:i+1]) <= 26: cur += a
a, b = b, cur
return bC++
int numDecodings(string s){
if(s.empty() || s[0]=='0') return 0;
int a = 1, b = 1; // dp[i-2], dp[i-1]
for(int i = 1; i < (int)s.size(); ++i){
int cur = s[i]!='0' ? b : 0;
int two = (s[i-1]-'0')*10 + (s[i]-'0');
if(two >= 10 && two <= 26) cur += a;
a = b; b = cur;
}
return b;
}Java
int numDecodings(String s){
if(s.isEmpty() || s.charAt(0)=='0') return 0;
int a = 1, b = 1; // dp[i-2], dp[i-1]
for(int i = 1; i < s.length(); i++){
int cur = s.charAt(i)!='0' ? b : 0;
int two = (s.charAt(i-1)-'0')*10 + (s.charAt(i)-'0');
if(two >= 10 && two <= 26) cur += a;
a = b; b = cur;
}
return b;
}复杂度
时间
O(n)
n=字符串长度,每位 O(1) 判两条来路
空间
O(1)
滚动只留前两项 a、b
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 解码方法 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
解码方法和爬楼梯 LeetCode 70 到底像在哪、差在哪?+
骨架都是 dp[i] = dp[i-1] + dp[i-2],把两条来路相加。差别是本题每条来路都带资格审查:单字符来路要求当前位不是 '0',两位来路要求组成的两位数落在 10~26,不合格的来路要按 0 算。爬楼梯两条路永远畅通,本题的路会随字符断掉,这也是它更容易在边界翻车的原因。
为什么解码方法能优化到 O(1) 空间?+
dp[i] 只用到紧邻的前两项 dp[i-1] 和 dp[i-2],更早的值再也用不上。用 a、b 两个变量分别存这两项,每算完一位就把 a 换成旧的 b、b 换成新算出的 cur,往前挪一格,整张 dp 数组不用开,空间从 O(n) 降到 O(1)。
开头或中间的 '0' 怎么判整串无解?+
开头是 '0' 时第一位就没有字母可对,直接返回 0。中间的 '0' 自己不能单独解码,唯一活路是和前一位组成 10 或 20(含 0 且落在 10~26 的两位数只有这两个);前一位若是 3 到 9,就凑成 30 到 90 全部超界,这个 '0' 两条来路皆断,整串解码数为 0。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 解码方法 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。