Problem overview
numDecodings receives a string s of digits. Each capital letter was written as its position in the alphabet (A is 1, B is 2, up to Z as 26). To decode, split the digits into groups, each group a number from 1 to 26 that is written without a leading zero, and turn each group into its letter. The function returns how many different ways there are, or 0 when the string cannot be decoded at all.
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, and neither 0 nor 06 is a letter.
Approach: count decodings of every beginning
Let dp[i] be the number of ways to decode the first i digits of s. The last letter of any decoding of those i digits is made from either the last digit alone or the last two digits, and nothing else is possible. That gives two independent cases, which are added:
- The last digit is a letter on its own. This works when
s[i - 1]is not'0'. Every decoding of the firsti - 1digits, followed by that letter, is a decoding of the firstidigits, so adddp[i - 1]. - The last two digits form one letter. This works when the number they make is from 10 to 26. Every decoding of the first
i - 2digits, followed by that letter, counts, so adddp[i - 2]. A pair such as07starts with 0 and is no letter, and 27 or more is beyond Z.
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 must count as one way, not zero. Then dp[1] is 1 if the first digit is not '0', and 0 otherwise. A string that starts with 0 stays at 0, and so does everything built on top of it.
The filled tables for two of the examples:
s = "226" dp = [1, 1, 2, 3]
s = "11106" dp = [1, 1, 2, 3, 2, 2]
In "226", dp[2] is 2: the digits "22" are "BB" or "V". Then dp[3] adds dp[2] (the 6 on its own) and dp[1] (the pair 26), giving 3. In "11106", the 0 at index 3 cannot stand alone, so dp[4] gets only the pair 10, which adds dp[2] (the two ways to decode "11"). Then dp[5] takes the 6 on its own, adding dp[4], while the pair 06 adds nothing. The result is 2.
Step by step
- Store the length in
n = len(s). - Create the table with
n + 1zeros:dp = [0] * (n + 1). One extra entry stands for the empty beginning. - Set
dp[0] = 1. - Set
dp[1]to 1 whens[0]is not'0'. - Loop over the rest with
for i in range(2, n + 1). Each entry needs the two before it, andn + 1makesdp[n]the last one filled. - Single digit. If
s[i - 1]is not'0', adddp[i - 1]todp[i]. - Read the pair. Outside that
if, take the last two digits as a number:two = int(s[i - 2:i]). - Two digits. If
10 <= two <= 26, adddp[i - 2]todp[i]. - Return
dp[n], the number of decodings of the whole string.
The solution in Python
class Solution:
def numDecodings(self, s: str) -> int:
n = len(s)
dp = [0] * (n + 1)
dp[0] = 1
if s[0] != '0':
dp[1] = 1
for i in range(2, n + 1):
if s[i - 1] != '0':
dp[i] += dp[i - 1]
two = int(s[i - 2:i])
if 10 <= two <= 26:
dp[i] += dp[i - 2]
return dp[n]
Python details worth a closer look:
- Slices.
s[i - 2:i]holds the characters at indicesi - 2andi - 1, the end index being excluded. For"226"andi = 3it is"26". intaccepts a leading zero.int("07")is 7, so a pair with a leading zero turns into a number below 10, which the range check then rejects. That is why the check uses 10 as the lower bound.- Chained comparison.
10 <= two <= 26tests both bounds at once. [0] * (n + 1)builds a list ofn + 1zeros. It is safe here because integers are immutable.- Digits are compared as characters.
s[i - 1] != '0'compares one-character strings, so there is no need to convert to numbers for the single-digit case. - Big counts are fine. Python integers don't overflow. The lesson's answers fit in 32 bits anyway.
Complexity
Time: O(n). The loop runs n - 1 times and each pass does a constant amount of work (the two-character slice is always two characters long).
Space: O(n). The table has n + 1 entries. Since each entry uses only the two before it, two variables could replace the table, but the table makes the idea easier to follow.
Edge cases and common mistakes
- A leading zero.
"06"and"0"give 0. Heredp[1]stays 0, and for"06"the pair 06 is 6, which is outside 10 to 26. - A zero in the middle.
"10"gives 1, because 0 can only follow a 1 or a 2 as part of 10 or 20."100"gives 0: the final 0 has no partner, since the pair 00 is not a letter and the single 0 is not either."30"gives 0 for the same reason. - Pairs above 26.
"27"gives 1: only 2 and 7, since 27 is no letter. - Starting
dp[0]at 0. Every count would stay 0, because everything is built on the empty beginning. - A single digit.
"1"gives 1. The loop doesn't run, anddp[1]is already set. - Checking only the pair. Accepting a pair like
06as 6 would count a decoding that the rules forbid.
Testing
"12"gives 2."226"gives 3."06"gives 0."11106"gives 2."10"gives 1."100"gives 0."27"gives 1."2101"gives 1."1111111111"(ten 1s) gives 89.
