Neetcode - Decode Ways

Neetcode - Decode Ways
Photo by Deva Darshan / Unsplash
Write code today that your future self will thank you for.

Decode Ways

A string consisting of uppercase english characters can be encoded to a number using the following mapping:

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"


To decode a message, digits must be grouped and then mapped back into letters using the reverse of the mapping above. There may be multiple ways to decode a message. For example, "1012" can be mapped into:

  • "JAB" with the grouping (10 1 2)
  • "JL" with the grouping (10 12)

The grouping (1 01 2) is invalid because 01 cannot be mapped into a letter since it contains a leading zero.

Given a string s containing only digits, return the number of ways to decode it. You can assume that the answer fits in a 32-bit integer.


Example 1:

Input: s = "12"

Output: 2

Explanation: "12" could be decoded as "AB" (1 2) or "L" (12).

Example 2:

Input: s = "01"

Output: 0

This problem can be quite tricky if you're not familiar with this kind of real-world character-to-number mapping. It requires a combination of recursive thinking, dynamic programming, string parsing, edge case handling, and even elements of backtracking. In short, it touches on several core concepts that are essential in programming interviews.

Below are a series of solutions, progressing from the most basic to the more optimized:

class SolutionRecursive {
public:
    int dfs(int i, string& s) {
        int ch = s[i];
        int size = s.size();
        
        if (ch == '0') return 0;
        if (i == size) return 1;

        int cnt = dfs(i + 1, s);
        if (i < size - 1) {
            if (ch == '1' ||
                (ch == '2' && s[i + 1] < '7')) {
                    cnt += dfs(i + 2, s);
            }
        }

        return cnt;
    }

    int numDecodings(string s) {
        return dfs(0, s);   
    }
};

Recursion - CSY

In this case, we start from index 0 and first check if the character at that position is '0' or if we've reached the end of the string. If these edge cases are cleared, we then recursively process the next index.

At each position, we consider one or two characters ahead to see if a valid 1-digit or 2-digit mapping can be formed. That’s essentially the core idea.

To add memoization, we simply introduce a memo table to store and reuse results for each index.

class SolutionRecursiveMemo {
    unordered_map<int, int> memo;
public:
    int dfs(int i, string& s) {
        int ch = s[i];
        int size = s.size();

        if (ch == '0') return 0;
        if (i == size) return 1;
        if (memo.count(i)) return memo[i];

        int cnt = dfs(i + 1, s);
        if (i < size - 1) {
            int ch_num = stoi(s.substr(i, 2));
            if (ch_num <= 26) {
                cnt += dfs(i + 2, s);
            }
        }

        return memo[i] = cnt;
    }

    int numDecodings(string s) {
        return dfs(0, s);   
    }
};

Recursion with Memo - CSY

Let’s also try the bottom-up approach. But wait… what exactly is bottom-up?
Well, here’s the idea:

Bottom-Up

  1. Build the solution starting from the smallest subproblems, typically from the end toward the beginning.
  2. Avoids recursion entirely by using iteration and filling up a DP table.
  3. Efficient in both time and space, especially when optimized with techniques like space reduction.
class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        vector<int> dp(n + 1, 0);

        dp[n] = 1;
        for (int i = n - 1; i >= 0; --i) {
            char ch = s[i];
            if (ch == '0') dp[i] = 0;
            else {
                dp[i] = dp[i + 1];

                if (i + 1 < n) {
                    int ch_num = stoi(s.substr(i, 2));
                    if (ch_num <= 26) {
                        dp[i] += dp[i + 2];
                    }
                }
            }
        }

        return dp[0];
    }
};

Bottom-Up DP - CSY

You can see that this approach starts from the end of the array. But honestly, it doesn’t feel all that different to me—since even the earlier methods also rely on results from future (or forward) steps and gradually build backward to form the final answer. After all, that’s a key characteristic of dynamic programming problems, isn’t it?

Conclusion

A brief note on DP.


It’s completely normal to feel anxious when encountering a DP problem during an interview. But take a deep breath and give yourself time. Nobody is perfect, and it’s okay to refer to solutions while practicing. What matters is that you keep working on it, even if it feels confusing at first. Staying stuck and nervous doesn’t help—be patient with yourself, and improvement will come with practice.

CSY

CSY

Nagoya, Japan