Problem overview
A message of capital letters was turned into digits by writing each letter as its position in the alphabet: A is 1, B is 2, up to Z as 26. Decoding means splitting the digits into groups, each a number from 1 to 26, and turning each group back into a letter. A group can't start with 0: "06" is not 6, and no letter is 0.
The method numDecodings receives the digit string s and returns how many ways it can be decoded, or 0 if there are none. For "12" the answer is 2: AB (1, 2) or L (12). For "226" it is 3: BZ (2, 26), VF (22, 6) or BBF (2, 2, 6). For "06" it is 0, because the first group would have to start with 0.
Approach: count decodings of every beginning
Look at the last group of a decoding of the first i digits. It is either the last digit alone (a letter if that digit is 1 to 9), or the last two digits together (a letter if they form a number from 10 to 26). Whatever comes before that group is a decoding of the first i - 1 or i - 2 digits. So the number of ways to decode the first i digits is
- the ways for the first
i - 1digits, if the last digit isn't0, plus - the ways for the first
i - 2digits, if the last two digits form a number from 10 to 26.
That is a table dp with dp[i] as the number of ways to decode the first i digits. Each entry needs only the two before it and a constant amount of work, so the table is filled in O(n) time. Trying every split by recursion without the table repeats the same suffixes many times and is exponential in the worst case.
The table starts with dp[0] = 1: the empty beginning has exactly one decoding, with no letters. Every longer decoding is built on it, so it has to count as one way, not zero.
Step by step
- Store the length of
sinn. - Create
dpas anint[]of sizen + 1, one more than the digits, becausedp[0]stands for the empty beginning. Java fills a newintarray with zeros. - Set
dp[0]to 1. - Set
dp[1]. If the first character isn't'0', one digit alone is a letter, sodp[1]is 1. If it is'0',dp[1]stays 0, and every count built on it does too. - Loop with
ifrom 2 up to and includingn. The first two entries are known, and the last one,dp[n], covers all ofs. - The last digit alone. If
s.charAt(i - 1)isn't'0', adddp[i - 1]todp[i]. - Read the last two digits as a number:
Integer.parseInt(s.substring(i - 2, i)), stored intwo. - The last two digits together. If
twois from 10 to 26, adddp[i - 2]todp[i]. A pair below 10, such as07, starts with 0 and is no letter, and neither is 27 or more. - Return
dp[n].
Here is the table for "226", from a real run of the solution's loop:
dp = [1, 1, 2, 3]
Entry 1 is 1 (the digit 2). Entry 2 is 2: the digit 2 on its own adds dp[1], and the pair 22 adds dp[0]. Entry 3 is 3: the digit 6 on its own adds dp[2] = 2, and the pair 26 adds dp[1] = 1. For "11106" the table is
dp = [1, 1, 2, 3, 2, 2]
and the answer is 2, matching AAJF and KJF.
The solution in Java
class Solution {
public int numDecodings(String s) {
int n = s.length();
int[] dp = new int[n + 1];
dp[0] = 1;
if (s.charAt(0) != '0') {
dp[1] = 1;
}
for (int i = 2; i <= n; i++) {
if (s.charAt(i - 1) != '0') {
dp[i] += dp[i - 1];
}
int two = Integer.parseInt(s.substring(i - 2, i));
if (two >= 10 && two <= 26) {
dp[i] += dp[i - 2];
}
}
return dp[n];
}
}
Java details worth a closer look:
charAtandcharcomparison.s.charAt(i - 1)returns achar, and!= '0'compares it with the character zero. Single quotes make achar;"0"with double quotes would be aString.substring(i - 2, i)takes the characters at positionsi - 2andi - 1: the end index is excluded.Integer.parseIntand leading zeros. It accepts them:Integer.parseInt("07")is7. That is why the code checks the range 10 to 26 and not just "at most 26": 7 is below 10, so the pair07is rejected.+=on an array element.dp[i] += dp[i - 1]adds to the entry in place. Both branches can fire for the samei, and then the entry is the sum of the two.intoverflow. Counts can grow like Fibonacci numbers, and anintwraps silently when it overflows. The lesson promises that the answer fits in 32 bits, sointis enough here. With 45 ones,"111...1", the answer is 1836311903, close to the limit of 2147483647.
Complexity
Time: O(n). The loop runs n - 1 times, and each pass does a constant amount of work: two character checks, one short substring and one parseInt.
Space: O(n) for the table. Each entry depends only on the two before it, so two variables would be enough, but the table makes the idea easier to follow.
Edge cases and common mistakes
- A leading zero.
"06"gives 0:dp[1]stays 0, the last digit 6 addsdp[1]= 0, and the pair 06 is 6, which is below 10, so nothing is added. - Zeros in the middle.
"10"gives 1, onlyJ, because the 0 can't stand alone and the pair 10 is a letter."100"gives 0: the pair 10 is a letter but leaves the last 0 alone, and the pair 00 is not a letter."30"and"60"also give 0, since 30 and 60 are above 26."101"gives 1, onlyJA. - Pairs above 26.
"27"gives 1, onlyBG: the pair 27 is not a letter. - One digit.
"1"gives 1 and"0"gives 0. The loop doesn't run, anddp[1]is the answer. This is why the table hasn + 1entries even forn == 1. - Treating
dp[0]as 0. Every count would be 0. The empty beginning is one way to decode nothing. - Checking only
two <= 26. That lets07and00through. The lower bound of 10 is what rules out a group that starts with 0. - An empty string. The lesson's strings have at least one character. For an empty string,
s.charAt(0)throwsStringIndexOutOfBoundsException.
Testing
"12"gives 2."226"gives 3."06"gives 0."11106"gives 2."10"gives 1, and"100"gives 0."27"gives 1, and"30"gives 0."2101"gives 1, and"101"gives 1."1"gives 1, and"0"gives 0.
