150NeetCode 150

105.Decode Ways

MediumBlind 751-D Dynamic Programming · 7/12

Problem

A message of letters was encoded with 'A' → "1", 'B' → "2", ..., 'Z' → "26". Given the digit string s, return the number of ways to decode it back into letters.

A valid split uses only the codes "1"–"26". A code can't have a leading zero, so "06" is invalid, and a lone "0" maps to nothing. If the string cannot be decoded at all, return 0.

Examples

Input:  s = "12"
Output: 2
Explanation: "AB" (1 2) or "L" (12).
Input:  s = "226"
Output: 3
Explanation: "BZ" (2 26), "VF" (22 6), "BBF" (2 2 6).
Input:  s = "06"
Output: 0
Explanation: "06" is not a valid code and "0" alone decodes to nothing.

Constraints

  • 1 <= s.length <= 100
  • s contains only digits and may contain leading zeros.
  • The answer fits in a 32-bit integer.

💡 Key Insight

Let f(i) be the number of ways to decode the suffix s[i..]. The first code starting at i is either one digit (valid if s[i] != '0') or two digits (valid if s[i..i+1] is between 10 and 26), so f(i) = (s[i] != '0' ? f(i + 1) : 0) + (10 <= s[i..i+1] <= 26 ? f(i + 2) : 0), with f(n) = 1 (the empty suffix has one decoding: stop). It is Climbing Stairs with conditions on each step, so it also reduces to two rolling variables.

Recursion (Brute Force)

TimeO(2ⁿ)SpaceO(n)

Intuition

At position i, try to peel off a one-digit code and a two-digit code, recursing on what's left. Every branch that reaches the end of the string is one full decoding.

Recurrence: f(i) = [s[i] != '0'] · f(i + 1) + [10 <= int(s[i..i+1]) <= 26] · f(i + 2), f(n) = 1.

Algorithm

  1. dfs(i): if i == n return 1; if s[i] == '0' return 0 (no code starts with 0).
  2. ways = dfs(i + 1).
  3. If i + 1 < n and s[i..i+1] is 10–26, add dfs(i + 2).
  4. Return ways; answer dfs(0).

Visualization

s = "2226". f(2) and f(3) are each computed more than once.

flowchart TD
    A["f(0) '2226'"] -->|"take 2"| B["f(1) '226'"]
    A -->|"take 22"| C["f(2) '26'"]
    B -->|"take 2"| D["f(2) '26'"]
    B -->|"take 22"| E["f(3) '6'"]
    C -->|"take 2"| F["f(3) '6'"]
    C -->|"take 26"| G["f(4) = 1"]
    D -->|"take 2"| H["f(3) '6'"]
    D -->|"take 26"| I["f(4) = 1"]
    E -->|"take 6"| J["f(4) = 1"]

f(3) = 1, f(2) = 2, f(1) = f(2) + f(3) = 3, f(0) = f(1) + f(2) = 5.

Code

java
class Solution {
    public int numDecodings(String s) {
        return dfs(s, 0);
    }

    // number of ways to decode s[i..]
    private int dfs(String s, int i) {
        if (i == s.length()) {
            return 1; // reached the end: one complete decoding
        }
        if (s.charAt(i) == '0') {
            return 0; // no code starts with '0'
        }
        int ways = dfs(s, i + 1); // single-digit code 1..9
        if (i + 1 < s.length()) {
            int two = (s.charAt(i) - '0') * 10 + (s.charAt(i + 1) - '0');
            if (two <= 26) { // already >= 10 because s[i] != '0'
                ways += dfs(s, i + 2);
            }
        }
        return ways;
    }
}

Complexity

  • Time: O(2ⁿ) — up to two branches per position.
  • Space: O(n) — recursion depth.

Complexity Comparison

Approach Time Space
Recursion (Brute Force) O(2ⁿ) O(n)
Top-Down Memoization O(n) O(n)
Bottom-Up Tabulation O(n) O(n)
Space-Optimized DP O(n) O(1)

Common Pitfalls

  • Treating "0" as a valid single-digit code — it decodes to nothing, so dp[i] = 0 when s[i] == '0'.
  • Accepting two-digit codes like "05" or "27": the pair must be in 10..26.
  • Setting the base case dp[n] = 0 — the empty suffix contributes exactly one way.
  • Using 0 as the memo sentinel — many suffixes genuinely have 0 decodings, so a 0 sentinel re-explores them.
  • Strings like "100" and "30" have answer 0; make sure your code returns it instead of crashing.