|
| 1 | +Daily Coding Problem #7 |
| 2 | +Problem |
| 3 | +This problem was asked by Facebook. |
| 4 | + |
| 5 | +Given the mapping a = 1, b = 2, ... z = 26, and an encoded message, count the number of ways it can be decoded. |
| 6 | + |
| 7 | +For example, the message '111' would give 3, since it could be decoded as 'aaa', 'ka', and 'ak'. |
| 8 | + |
| 9 | +You can assume that the messages are decodable. For example, '001' is not allowed. |
| 10 | + |
| 11 | +Solution |
| 12 | +This looks like a problem that is ripe for solving with recursion. First, let's try to think of a recurrence we can use for this problem. We can try some cases: |
| 13 | + |
| 14 | +"", the empty string and our base case, should return 1. |
| 15 | +"1" should return 1, since we can parse it as "a" + "". |
| 16 | +"11" should return 2, since we can parse it as "a" + "a" + "" and "k" + "". |
| 17 | +"111" should return 3, since we can parse it as: |
| 18 | +"a" + "k" + "" |
| 19 | +"k" + "a" + "" |
| 20 | +"a" + "a" + "a" + "". |
| 21 | +"011" should return 0, since no letter starts with 0 in our mapping. |
| 22 | +"602" should also return 0 for similar reasons. |
| 23 | +We have a good starting point. We can see that the recursive structure is as follows: |
| 24 | + |
| 25 | +If string starts with zero, then there's no valid encoding. |
| 26 | +If the string's length is less than or equal to 1, there is only 1 encoding. |
| 27 | +If the first two digits form a number k that is less than or equal to 26, we can recursively count the number of encodings assuming we pick k as a letter. |
| 28 | +We can also pick the first digit as a letter and count the number of encodings with this assumption. |
| 29 | +def num_encodings(s): |
| 30 | + if s.startswith('0'): |
| 31 | + return 0 |
| 32 | + elif len(s) <= 1: # This covers empty string |
| 33 | + return 1 |
| 34 | + |
| 35 | + total = 0 |
| 36 | + |
| 37 | + if int(s[:2]) <= 26: |
| 38 | + total += num_encodings(s[2:]) |
| 39 | + |
| 40 | + total += num_encodings(s[1:]) |
| 41 | + return total |
| 42 | +However, this solution is not very efficient. Every branch calls itself recursively twice, so our runtime is O(2n). We can do better by using dynamic programming. |
| 43 | + |
| 44 | +All the following code does is repeat the same computation as above except starting from the base case and building up the solution. Since each iteration takes O(1), the whole algorithm now takes O(n). |
| 45 | + |
| 46 | +from collections import defaultdict |
| 47 | + |
| 48 | +def num_encodings(s): |
| 49 | + # On lookup, this hashmap returns a default value of 0 if the key doesn't exist |
| 50 | + # cache[i] gives us # of ways to encode the substring s[i:] |
| 51 | + cache = defaultdict(int) |
| 52 | + cache[len(s)] = 1 # Empty string is 1 valid encoding |
| 53 | + |
| 54 | + for i in reversed(range(len(s))): |
| 55 | + if s[i].startswith('0'): |
| 56 | + cache[i] = 0 |
| 57 | + elif i == len(s) - 1: |
| 58 | + cache[i] = 1 |
| 59 | + else: |
| 60 | + if int(s[i:i + 2]) <= 26: |
| 61 | + cache[i] = cache[i + 2] |
| 62 | + cache[i] += cache[i + 1] |
| 63 | + return cache[0] |
0 commit comments