# time O(N) space O(N)
class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        dp = [0]*(n+1)
        # case 1  not 0 
        # case 2   0 
        # dp[i] += dp[i-1]  || += dp[i-2] , dp[i]: there are dp[i] case to decode for string s[:i]
        dp[0] = 1 # special case to ensure dp[2] is valid 
        dp[1] = 1 if s[0] != '0' else 0 # start base case dp[1], dp[2]
        for i in range(2,n+1):
            first = s[i-1:i]
            second = s[i-2:i]
            if 1 <= int(first) <= 9:
                dp[i] += dp[i-1]
            if 10 <= int(second) <= 26:
                dp[i] += dp[i-2] 
        return dp[n]

recursion way time/space O(N)

# memoization

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        memo = [None]*n
        def helper(pos,s,mem):
            n = len(s)
            if pos == n:
                return 1
            if s[pos] == '0':
                return 0
            if mem[pos] != None:
                return mem[pos]
            # decode with 1 digit 
            res = helper(pos+1,s,mem)
            # decode with 2 digits 
            if pos < n-1 and ( s[pos] =='1' or (s[pos] == '2' and s[pos+1] < '7')):
                res += helper(pos+2,s,mem)
            mem[pos] = res
            return mem[pos]
        return helper(0,s,memo)