Skip to content

Coding patternsBlind 75

Decode Ways

The last letter of a decoding uses one digit or two, so the count for each prefix is built from the two shorter prefixes; keep just those two numbers. O(n) time and O(1) space, with a step-through, code in five languages and the Big O worked out from the code.

Letters are turned into numbers with A = 1, B = 2, and so on up to Z = 26. Then the numbers are written one after another, with no spaces. You are given such a string of digits digits. Return how many different letter strings could have produced it.

A letter’s number never starts with 0, so “06” is not F. If no letter string fits, return 0.

Example 1

Input: digits = "2261"

Output: 3

2 2 6 1 is BBFA, 22 6 1 is VFA, and 2 26 1 is BZA. 61 is too big to be a letter.

digits
20216213

Example 2

Input: digits = "06"

Output: 0

The first digit is 0, and no letter’s number starts with 0.

Example 3

Input: digits = "110"

Output: 1

The 0 must pair with the 1 before it as 10 (J). So the only reading is 1 10, which is AJ.

  • 1 ≤ digits.length ≤ 100 (call it n)
  • s holds only digits.
  • The answer fits in a 32-bit integer.
Can 0 be a letter on its own?
No. A 0 must be the second digit of 10 or 20, or there is no decoding.
Is "05" the letter E?
No. A leading zero is not allowed, so "05" is not a letter.
Do we list the decodings?
No, only count them.
Can the string start with 0?
Yes, and then the answer is 0.

Look at the next letter. It uses one digit, or two digits that form 10 to 26. Either way, what is left is a shorter version of the same problem. So the count is built from counts of smaller pieces, and those pieces repeat across choices. That is dynamic programming, and it has the same shape as Climbing Stairs, where each step is 1 or 2, but with rules about which steps are allowed.

There is no middle tab here. Adding a memo to the recursion, or filling a table, costs O(n) time like the two-number version, but O(n) memory. Climbing Stairs shows that step in detail.

Two numbers O(n)

Turn it around and grow the string from the left. Count the decodings of each prefix: the first digit, then the first two digits, and so on. The last letter of a prefix uses its last digit, or its last two digits:

  • If the last digit is not 0, every decoding of the prefix one digit shorter can be extended with it.
  • If the last two digits form 10 to 26, every decoding of the prefix two digits shorter can be extended with them.

Add the two. Only the two previous counts are ever needed, so keep them as two_back and one_back. Start with 1 for the empty prefix, and 1 or 0 for the first digit, depending on whether it is 0.

Why it is correct: it applies the same split as the recursion, from the other end, and each count it needs is already known when it is used.

digits
20216213
  1. two_back, one_back = 1, (1 if digits[0] != "0" else 0)
  2. for i in 1 .. len(digits) - 1:
  3. current = 0
  4. if digits[i] != "0": current += one_back
  5. if digits[i-1:i+1] is 10 to 26:
  6. current += two_back
  7. two_back, one_back = one_back, current
  8. return one_back

Go left to right and count the decodings of each prefix. The last letter of a prefix uses its last digit, or its last two digits. So a prefix only needs the counts for the prefixes one and two digits shorter.

1 / 9
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Two numbers, left to right: the ways to decode a prefix come from the prefix
// one digit shorter and the one two digits shorter. Keep just those two. O(n) time, O(1) space.
public int numDecodings(String digits) {
// Ways for the empty prefix, and for the first digit alone.
int twoBack = 1, oneBack = digits.charAt(0) == '0' ? 0 : 1;
for (int i = 1; i < digits.length(); i++) {
int current = 0;
// Digit i on its own is a letter, unless it is 0.
if (digits.charAt(i) != '0') current += oneBack;
// Digits i - 1 and i together are a letter from 10 to 26.
int twoDigit = (digits.charAt(i - 1) - '0') * 10 + (digits.charAt(i) - '0');
if (twoDigit >= 10 && twoDigit <= 26) current += twoBack;
twoBack = oneBack;
oneBack = current;
}
return oneBack;
}
}
Time O(n)Space O(1)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    The loop body does two checks, up to two additions and two moves. That is a fixed job, so each run of the loop counts as 1 step.

    1. two_back, one_back = 1, (1 if digits[0] != "0" else 0)
    2. for i in 1 .. len(digits) - 1:
    3. current = 0
    4. if digits[i] != "0": current += one_back
    5. if digits[i-1:i+1] is 10 to 26:
    6. current += two_back
    7. two_back, one_back = one_back, current
    8. return one_back
  2. 2 Count the runs on Example 1

    The loop runs for positions 1, 2 and 3.

    3 runs.

    i123
    loop runs111
    total
    3 runs
  3. 3 Write the count for any n

    The loop goes from 1 to n − 1:

    n − 1 runs

    Check it with Example 1: 4 − 1 = 3. That matches the count.

  4. 4 Keep only the part that grows fastest

    Drop the − 1. What is left is n.

    So the time is O(n).

  5. S Space: count the extra memory the same way

    Three numbers, whatever n is. So the space is O(1).

    two_backone_backcurrent

2,000 random strings of 1 to 16 digits made of 0, 1, 2, 6 and 7, the digits on the edges of the rules. The plain recursion and the two numbers gave the same answer every time.

Largest inputsResult
100 digits (the most allowed): 44 ones then 56 nines, for 1,836,311,903 ways, which still fits in 32 bits. Two numbers.Under 0.001 s, and under 0.001 MB of extra memory: just the two numbers.
24 ones. Plain recursion, about 100,000 calls.0.02 s. Each extra 1 makes about 1.6 times more calls, so 100 digits is far out of reach.