LC 91 - Decode Ways
Question
You have intercepted a secret message encoded as a string of numbers. The message is decoded via the following mapping:
"1" -> 'A' "2" -> 'B' ... "25" -> 'Y' "26" -> 'Z'
However, while decoding the message, you realize that there are many different ways you can decode the message because some codes are contained in other codes ("2" and "5" vs "25").
For example, "11106" can be decoded into:
"AAJF"with the grouping(1, 1, 10, 6)"KJF"with the grouping(11, 10, 6)- The grouping
(1, 11, 06)is invalid because"06"is not a valid code (only"6"is valid).
Note: there may be strings that are impossible to decode.
Given a string s containing only digits, return the number of ways to decode it. If the entire string cannot be decoded in any valid way, return 0.
The test cases are generated so that the answer fits in a 32-bit integer.
Example 1:
1
2
Input: s = "12"
Output: 2
Explanation:
“12” could be decoded as “AB” (1 2) or “L” (12).
Example 2:
1
2
Input: s = "226"
Output: 3
Explanation:
“226” could be decoded as “BZ” (2 26), “VF” (22 6), or “BBF” (2 2 6).
Example 3:
1
2
Input: s = "06"
Output: 0
Explanation:
“06” cannot be mapped to “F” because of the leading zero (“6” is different from “06”). In this case, the string is not a valid encoding, so return 0.
Constraints:
1 <= s.length <= 100scontains only digits and may contain leading zero(s).
Links
Question here and solution here
Solution
concept
top down: memoization
This is the recursive solution where we cache the intermedia results. The key idea is to track if we can proceed i + 1 or i + 2 depend on the conditions and sum these possibilities at each index and cache it for later usage.
bottom up
This is very similar to the memoization solution in terms of logic. We will iterate the string in reverse directions (i.e. solve sub problems).
code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
class Solution:
"""
top down: memoization
"""
def dfs(self, i, s):
"""
i means string start from i -> eos, to find how many ways of decode can be found for this substring
"""
# if this index already solved -> obtained num of ways of decode
# or we reached (beyond) end of str
if i in self.dp:
return self.dp[i]
# if char is 0, then it is not possible to decode
if s[i] == "0":
return 0
# go for next char
result = self.dfs(i + 1, s)
# if i + 1 never out of bound and curr one is either 1 or 2 (2 must also satsify the next char is in 0-6)
if i + 1 < len(s) and (s[i] == "1" or (s[i] == "2" and s[i+1] in "0123456")):
# num of ways from the previous one is being added tgt
result += self.dfs(i + 2, s)
# update the cache, this is the dp part
self.dp[i] = result
return result
def numDecodings(self, s: str) -> int:
"""
dp (recursive) approach
time complexity O(n)
space complexity O(n)
note the way dfs works is to process the str backwards (i.e. s[-1] first)
"""
# dp cache to store num of decode ways at each index
# this is the base case where if our step reach beyond the len of s
# then it return 1 (way of decoding)
# also edge case for empty s
self.dp = {len(s): 1}
# start at index 0
result = self.dfs(0, s)
return result
class Solution:
"""
top-down: memoization
"""
def numDecodings(self, s: str) -> int:
cache = {}
def dfs(i):
if i in cache:
return cache[i]
if i == len(s):
return 1
if i > len(s) or s[i] == "0":
return 0
cache[i] = dfs(i + 1)
if s[i] == "1" or (i+1 < len(s) and s[i] == "2" and s[i+1] in "0123456"):
cache[i] += dfs(i + 2)
return cache[i]
return dfs(0)
class NeetSolution:
"""
bottom up solution
time complexity O(n)
space complexity O(1)
"""
def numDecodings(self, s: str) -> int:
# edge case for empty s
# also base case for dp
dp = {len(s): 1}
# loop backwards -> start from last char
for i in range(len(s) - 1, -1, -1):
# if char is 0, then it is not possible to decode
if s[i] == "0":
dp[i] = 0
# if curr char is 1->9, then it is possible to decode
# we put the previous solved index (i+1 since we are going backwards) into the current index first
else:
dp[i] = dp[i + 1]
# same condition as the recursive approach
if i + 1 < len(s) and (s[i] == "1" or s[i] == "2" and s[i + 1] in "0123456"):
# same pos we add on the previous solved index (i+2 since we are going backwards)
dp[i] += dp[i + 2]
return dp[0]
class Solution:
"""
bottom up solution
"""
def numDecodings(self, s: str) -> int:
dp = [0] * (len(s) + 1)
dp[-1] = 1
for i in range(len(dp)-2, -1, -1):
if s[i] == "0":
dp[i] = 0
else:
dp[i] = dp[i + 1]
if i + 1 < len(s) and (s[i] == "1" or s[i] == "2" and s[i + 1] in "0123456"):
dp[i] += dp[i + 2]
return dp[0]
Complexity
time: $O(n)$
space: $O(n)$