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, which is 26. The function numDecodings receives a string s of digits and returns the number of ways to split it into groups, each a number from 1 to 26, so that every group stands for a letter. A group can't start with 0: "06" is not the same as "6", and no letter is 0. If the string can't be decoded, the answer is 0.
For s = "12" the answer is 2: the groups 1, 2 give AB, and the single group 12 gives L. For s = "226" the answer is 3: 2, 26 gives BZ; 22, 6 gives VF; 2, 2, 6 gives BBF. For s = "06" the answer is 0, because the first group would have to begin with 0.
Approach: count decodings of every beginning
The last group of any decoding is either one digit or two digits. That splits the problem into smaller copies of itself. Let dp[i] be the number of ways to decode the first i digits of s. To decode the first i digits:
- If the last digit,
s[i-1], is not'0', it can be a letter on its own. Every decoding of the firsti - 1digits, followed by this letter, is a decoding of the firsti. That addsdp[i-1]. - If the last two digits form a number from 10 to 26, they can be one letter. Every decoding of the first
i - 2digits, followed by it, is a decoding of the firsti. That addsdp[i-2].
The two cases never overlap, since one ends in a one-digit group and the other in a two-digit group, so the counts are added. A pair such as 07 starts with 0 and is no letter, and a pair such as 27 is above 26. Neither adds anything.
Trying every split with plain recursion repeats the same suffixes many times, and the work grows exponentially. The table computes each dp[i] once from the two before it, which gives the O(n) time the statement asks for.
For "226" the table, from dp[0] to dp[3], is [1 1 2 3], and for "11106" it is [1 1 2 3 2 2].
Step by step
- Store the length in
n := len(s). - Create the table
dp := make([]int, n+1):n + 1zeros.dp[0]stands for the empty beginning, before any digit. - Set
dp[0] = 1. The empty beginning has exactly one decoding, with no letters. Every longer decoding builds on it, so it must count as one way and not zero. - Handle the first digit. If
s[0] != '0', setdp[1] = 1. One digit from 1 to 9 is a letter. If the string starts with 0,dp[1]stays 0, and so does every count built on it. - Loop from 2 to
nwithfor i := 2; i <= n; i++. The entriesdp[0]anddp[1]are known, each later one needs the two before it, and the last one,dp[n], covers all ofs. - Use the last digit alone. If
s[i-1] != '0', adddp[i-1]todp[i]with+=. - Read the last two digits as a number.
two, _ := strconv.Atoi(s[i-2 : i])converts the slices[i-2 : i], which holds the characters ati-2andi-1.Atoireturns the number and an error. The two characters are always digits, so the error is dropped with_. - Use the pair if it is a letter. If
two >= 10 && two <= 26, adddp[i-2]todp[i]. - Return
dp[n]after the loop.
The solution in Go
func numDecodings(s string) int {
n := len(s)
dp := make([]int, n+1)
dp[0] = 1
if s[0] != '0' {
dp[1] = 1
}
for i := 2; i <= n; i++ {
if s[i-1] != '0' {
dp[i] += dp[i-1]
}
two, _ := strconv.Atoi(s[i-2 : i])
if two >= 10 && two <= 26 {
dp[i] += dp[i-2]
}
}
return dp[n]
}
The Go details worth knowing:
- Indexing a string gives a byte.
s[0]has typebyte, the numeric code of the character, so it is compared with the character literal'0', in single quotes. Comparing it with the string"0"would not compile. Because the string holds only ASCII digits, one byte is one character. - String slices.
s[i-2 : i]is a substring from indexi-2up to, but not including,i. It shares the original's memory, so no copy is made. For"226"andi = 3, it is"26". strconv.Atoiand leading zeros.Atoi("07")returns7with no error, so the range testtwo >= 10is what rejects pairs that start with 0.Atoialso accepts a leading+or-, but the characters here are always digits.- The blank identifier.
two, _ := ...discards the second return value. Go requires every declared variable to be used, so an error variable you never read would be a compile error. make([]int, n+1). It creates a slice ofn + 1integers, all 0, ready to use. The+=operations rely on those zeros.- Integer size.
intis 64 bits, so the counts never overflow for strings of up to 100 digits, and the statement promises the answer fits in 32 bits.
Complexity
Time: O(n). The loop runs n - 1 times, and each round does a constant amount of work: two comparisons, a two-character conversion and at most two additions.
Space: O(n). The table holds n + 1 integers. Since dp[i] looks only at the two previous entries, two variables would be enough for O(1) space, but the table makes the idea easier to follow.
Edge cases and common mistakes
- A leading zero.
"06"gives0and"0"gives0. The first digit can't be a letter, and06is not a valid pair. - A zero in the middle.
"10"gives1, since the 0 can only be the end of the group 10."100"gives0, because the groups would have to be 10 and 0, and 0 is no letter, while 00 isn't a letter either."30"gives0, because 30 is above 26 and 0 alone is not a letter. - Pairs above 26.
"27"gives1: only the groups 2 and 7. The pair 27 is not a letter. - A single digit.
"1"gives1. The loop does not run, anddp[1]was set before it. - Counts growing like Fibonacci numbers.
"1111111111"(ten 1s) gives89, and its table is[1 1 2 3 5 8 13 21 34 55 89]. Every pair 11 is a letter, so each entry is the sum of the two before it. - A zero that blocks a path.
"2101"gives1, and"230"gives0. - Setting
dp[0]to 0. Every count would stay 0, because everything builds on the empty beginning. - Using
ifandelse. The one-digit and two-digit cases are independent, and both can add todp[i]. Two separateifstatements are correct. - Forgetting the lower bound 10. Without
two >= 10, a pair like07would be counted as the letter G with a leading zero.
Testing
s = "12"gives2.s = "226"gives3.s = "06"gives0.s = "11106"gives2.s = "10"gives1.s = "100"gives0.s = "27"gives1.s = "2101"gives1.s = "1111111111"gives89.s = "2611055971756562"gives4.
