Problem overview
numDecodings(s) receives a string of digits. Each capital letter was written as its position in the alphabet, A as 1 up to Z as 26, and the digits were run together. The function returns how many different ways the digits can be split into groups, each a number from 1 to 26, and read back as letters. A group can't start with 0, so "06" is not the same as "6". If the string can't be decoded at all, the answer is 0.
For "12" the answer is 2: "AB" from 1 and 2, or "L" from 12. For "226" it is 3: "BZ" (2, 26), "VF" (22, 6) and "BBF" (2, 2, 6). For "06" it is 0, because the first group would have to start with 0.
Approach: count the ways for each beginning of the string
Look at the end of a beginning of s. The last group is either one digit or two. If it is one digit (1 to 9), then the digits before it can be decoded in any of the ways counted for the beginning that is one digit shorter. If it is two digits forming a number from 10 to 26, the ways of the beginning that is two digits shorter apply. Adding the two cases gives the count.
So let dp[i] be the number of ways to decode the first i digits. Then dp[i] is the sum of up to two terms:
dp[i - 1], if digiti - 1(the last of the beginning) is not'0';dp[i - 2], if the last two digits make a number from 10 to 26.
The table starts with dp[0] = 1: the empty beginning has exactly one decoding, with no letters, and every longer decoding is built on it. And dp[1] is 1 when the first digit isn't '0', else 0. For "226" the table goes from 1,1,0,0 to 1,1,2,0 at i = 2 (both "2, 2" and "22" work) and to 1,1,2,3 at i = 3. Here the last digit 6 adds dp[2] = 2 and the pair 26 adds dp[1] = 1.
Step by step
- Store the length in
const n = s.length. - Create the table:
const dp = new Array(n + 1).fill(0). It has one entry more than there are digits, becausedp[0]stands for the empty beginning.constkeeps the same array; its entries can still change. - Set
dp[0] = 1. - First digit: if
s[0] !== '0', setdp[1] = 1. When the string starts with 0,dp[1]stays 0 and so does every count built on it. - Loop with
let i = 2whilei <= n. The first two entries are known, and the last one,dp[n], is the answer. - The last digit alone: if
s[i - 1] !== '0', adddp[i - 1]todp[i]with+=. - The last two digits: take
s.substring(i - 2, i), convert it withNumberand store it inconst two. - A valid pair: if
two >= 10 && two <= 26, adddp[i - 2]todp[i]. - Return
dp[n].
The solution in JavaScript
class Solution {
numDecodings(s) {
const n = s.length;
const dp = new Array(n + 1).fill(0);
dp[0] = 1;
if (s[0] !== '0') {
dp[1] = 1;
}
for (let i = 2; i <= n; i++) {
if (s[i - 1] !== '0') {
dp[i] += dp[i - 1];
}
const two = Number(s.substring(i - 2, i));
if (two >= 10 && two <= 26) {
dp[i] += dp[i - 2];
}
}
return dp[n];
}
}
Details worth knowing:
- Digits are characters.
s[0]is a one-character string, so it is compared with'0'and not with the number 0.!==compares strictly, with no type conversion. substring(i - 2, i)takes the characters at indexesi - 2andi - 1; the end index is excluded.Number("07")is 7. A leading zero is dropped, so the pair"07"gives 7, which is below 10 and rejected. That is the same rule as "a group can't start with 0". The number 27 or more is rejected by the upper limit.new Array(n + 1).fill(0).new Array(n + 1)alone has empty slots;fill(0)puts a zero in each one so that+=works.
Complexity
Time: O(n). The loop runs n - 1 times and each pass does constant work.
Space: O(n) for the table. Each entry only needs the two before it, so two variables would be enough if the table mattered.
Edge cases and common mistakes
- A leading zero.
"06"gives 0 and"0"gives 0. - A zero in the middle.
"10"gives 1: only the group 10 works, since the digit 0 can't stand alone."100"gives 0, because the last 0 can't stand alone and the pair 00 isn't a number from 10 to 26."301"gives 0 too. - Pairs above 26.
"27"gives 1: only 2 and 7 work, as 27 is not a letter. - A single digit.
"7"gives 1. The loop doesn't run anddp[1]is returned. - A zero that is fine.
"2101"gives 1: only 2, 10, 1 works. - Starting
dp[0]at 0. Every count would stay 0, because every decoding is built on the empty one. - Adding
dp[i - 2]for pairs like"05". The check for 10 to 26 prevents it.
Testing
"12"gives 2."226"gives 3."06"gives 0."11106"gives 2."10"gives 1."100"gives 0."27"gives 1.
