[
string
dp
]
Leetcode 0639. Decode Ways II
Problem statement
https://leetcode.com/problems/decode-ways-ii/
Solution
Classical dynamic programming problem, where we need to carefully connect dp_new with dp. There is more clean solution, if we keep 3 states: dp[0] is number of total ways,dp[1] is number of decodes, ending with 1, which is not used yet and dp[2] is number of decodes, ending with 2, which is not used yet. Then we can do the following recursion:
- If
c == "*"then there can be two options: if last number has only one digit, then we have9*dp[0]options. Also, if we try go group last two digits, we have9*dp[1]options for...1*case and6*dp[2]options for...2*case. To evaluate newdp[1]anddp[2], we just returndp[0]. - If
c != "*"then total number of options is(c > '0') * dp[0]for single digit last number or we could pair it with1or with2if it is not more than6. To evaluate newdp[1], we check if last digit is1and fordp[2], we check if last digit is2.
Complexity
Time complexity is O(n), space complexity is O(1).
Code
class Solution(object):
def numDecodings(self, S):
mod, dp = 10**9 + 7, [1, 0, 0]
for c in S:
dp_new = [0,0,0]
if c == '*':
dp_new[0] = 9*dp[0] + 9*dp[1] + 6*dp[2]
dp_new[1] = dp[0]
dp_new[2] = dp[0]
else:
dp_new[0] = (c > '0') * dp[0] + dp[1] + (c <= '6') * dp[2]
dp_new[1] = (c == '1') * dp[0]
dp_new[2] = (c == '2') * dp[0]
dp = [i % mod for i in dp_new]
return dp[0]
Solution 2
Previous solution is very fast and efficient, but I think it is a bit difficult to invent it in short time. So, here is an alternative: let dp(i) be the number of ways to decode s[:i]. Then we can have several cases:
- if
i < 0, we return1, there is only one way to decode empty string. - if
s[i] == *, then there is9*dp(i-1)ways to decode if we take last number 1-digit. To decode last number 2-digit we need to consider several cases. If we have1*, then we have9ways to replace*with[1, 2, ..., 9]. If we have2*, then we have6ways to replace it with[1, 2, ..., 6]. Finally, if we have**, then we have15options:[11, ...., 19, 21, ..., 26]. - if
s[i] != *, that it it is digit, we havedp(i-1)ways to decode if we have last digit not equal to0and 0 ways if it is zero. For last2digits we need again consider cases: if previous element is digit, then only case[10, ..., 26]will be good for as, so we adddp(i-2). If we have*., then if. = 0, 1, 2, 3, 4, 5, 6, then we have2options for*: 1 and 2, in other case we have only one option.
Complexity
It is O(n) for time and space.
Code
class Solution:
def numDecodings(self, s):
M = 10**9 + 7
@lru_cache(None)
def dp(i):
if i < 0: return 1
if s[i] == "*":
corr = {"1": 9, "2": 6, "*":15}
ans = 9*dp(i-1)
if i > 0: ans += corr.get(s[i-1], 0)*dp(i-2)
return ans % M
ans = dp(i-1) if s[i] != "0" else 0
if i > 0 and "10" <= s[i-1:i+1] <= "26":
ans += dp(i-2)
elif i > 0 and s[i-1] == "*":
ans += (2 if s[i] <= "6" else 1)*dp(i-2)
return ans % M
return dp(len(s)-1) % M