class Solution {
  private static final int EMPTY = -1;

  private static int charToDigit(String s, int i) {
    return s.charAt(i) - '0';
  }

  private int numDecodings(String s, int[] dp, int i) {
    // out of bounds
    if (i < 0 || i > s.length()) {
      return 0;
    }

    // final digit
    if (i == s.length()) {
      return 1;
    }

    // starting with 0, not allowed
    if (s.charAt(i) == '0') {
      return 0;
    }

    // solution has been already precomputed
    if (dp[i] != EMPTY) {
      return dp[i];
    }

    // taking just one digit
    int result = numDecodings(s, dp, i + 1);

    // taking two digits
    if (i + 1 < s.length() && charToDigit(s, i) * 10 + charToDigit(s, i + 1) <= 26) {
      result += numDecodings(s, dp, i + 2);
    }

    // save and return
    dp[i] = result;
    return result;
  }

  public int numDecodings(String s) {
    int[] dp = new int[s.length()];
    for (int i = 0; i < dp.length; ++i) {
      dp[i] = EMPTY;
    }

    return numDecodings(s, dp, 0);
  }
}