Problem overview
numDecodings receives a string s of digits and returns how many ways it can be split into groups that are each a number from 1 to 26, where 1 is A, 2 is B, up to 26 for Z. A group can't start with 0, so "06" isn't a 6. If no split works, the answer is 0.
For "12" the answer is 2: the groups 1, 2 (AB) or the single group 12 (L). For "226" it is 3: 2, 26 or 22, 6 or 2, 2, 6. For "06" it is 0, because the first group would start with 0.
Approach: count decodings of every beginning
Let dp[i] be the number of ways to decode the first i digits. The last group of any decoding of those digits is either one digit or two:
- If digit
iisn't0, it can be a letter alone, and every decoding of the firsti - 1digits extends with it: adddp[i - 1]. - If digits
i - 1anditogether make a number from 10 to 26, they can be one letter, and every decoding of the firsti - 2digits extends with it: adddp[i - 2].
Pairs below 10 (such as 07) start with 0 and are no letter, and pairs above 26 don't exist as letters. A 0 can therefore only be used as the second digit of 10 or 20.
The base case is dp[0] = 1: the empty beginning has exactly one decoding, the empty one. Without it, no longer count could be built. Each entry needs only the two before it, so one pass is enough.
For "226" the table is:
dp = [1, 1, 2, 3]
The entry for the first digit is 1, for 22 it is 2 (2, 2 or 22), and for 226 it is 3. For "11106" the table is [1, 1, 2, 3, 2, 2], ending in 2: AAJF and KJF.
Step by step
- Get the characters. A Swift
Stringcan't be indexed with a number, soArray(s)copies its characters intochars. - Count them into
n. - Create the table.
dpis[Int](repeating: 0, count: n + 1): one entry more than digits, where entry 0 stands for the empty beginning. - Base case. Set
dp[0]to 1. - First digit. If
chars[0]isn't"0", setdp[1]to 1. A string starting with 0 leaves it at 0, and every count built on it stays 0. - Loop
iover2..<n + 1, so the last value isn. - One-digit group. If
chars[i - 1]isn't"0", adddp[i - 1]todp[i]. - Two-digit number. Compute
twofrom the digits ati - 2andi - 1: the first times 10 plus the second, each taken withwholeNumberValue!. - Two-digit group. If
twois between 10 and 26, adddp[i - 2]todp[i]. - Return
dp[n].
The solution in Swift
class Solution {
func numDecodings(_ s: String) -> Int {
let chars = Array(s)
let n = chars.count
var dp = [Int](repeating: 0, count: n + 1)
dp[0] = 1
if chars[0] != "0" {
dp[1] = 1
}
for i in 2..<n + 1 {
if chars[i - 1] != "0" {
dp[i] += dp[i - 1]
}
let two = chars[i - 2].wholeNumberValue! * 10 + chars[i - 1].wholeNumberValue!
if two >= 10 && two <= 26 {
dp[i] += dp[i - 2]
}
}
return dp[n]
}
}
The Swift details worth a closer look:
- Strings have no integer subscript. Writing
s[1]on aStringdoesn't compile: the error is "cannot subscript String with an Int, use a String.Index instead".Array(s)makes an array ofCharactervalues, which can be indexed by position, andchars.countis the number of characters. - Comparing with a literal.
chars[i - 1] != "0"compares aCharacterwith the literal"0", which Swift reads as aCharacterhere. wholeNumberValue. It is a property ofCharacterthat returns anInt?: the value of a digit, ornilfor a character that isn't one. The input here is digits only, so!never fails. For the characters"2"and"6",twois 26, and for"0"and"7"it is 7.- Operator precedence. In
2..<n + 1, the addition happens first, so the range is2..<(n + 1). Forn == 1it is2..<2, which is empty and fine. A range whose lower bound is above its upper bound, such as2..<1, would crash. - Table of
Int.Intis 64 bits in Swift, and overflow crashes the program instead of wrapping. The lesson's answers fit in 32 bits, so there is no risk here.
Complexity
Time: O(n). Each of the n entries needs a constant amount of work, and copying the characters is O(n) as well.
Space: O(n). The table and the character array each hold about n entries. Since dp[i] only reads the two entries before it, two variables would be enough, but the table is easier to read.
Edge cases and common mistakes
- Leading zero.
"06"and"0"both give 0. - A zero in the middle.
"10"gives 1 (only the group 10), and"100"gives 0: the last 0 can't stand alone, and00is not a letter, so no split works."30"also gives 0, because 30 is above 26. - Numbers above 26.
"27"gives 1: only 2 and 7."12"gives 2. - A single digit.
"1"gives 1. The loop doesn't run, and the answer comes fromdp[1]. - Treating a 0 as a digit-by-digit letter. Adding
dp[i - 1]for a0would count a letter that doesn't exist. - Using
two >= 1instead oftwo >= 10. A pair such as07would count as the number 7, but a group can't start with 0. - Starting with
dp[0] = 0. Every count would stay 0.
Testing
"12"gives 2."226"gives 3."06"gives 0."11106"gives 2."0"gives 0, and"1"gives 1."10"gives 1, and"100"gives 0."27"gives 1, and"30"gives 0."2101"gives 1.
