A message containing letters from A-Z can be encoded into numbers using the following mapping:   'A' -> '1' 'B' -> '2' 'C' -> '3' ... ... 'Z' -> '26' To decode an encoded message, all the digits must be grouped then mapped back into letters using the reverse of the mapping above (there may be multiple ways). For example, "11106" can be mapped into:

Note that the grouping (1 11 06) is invalid because "06" cannot be mapped into 'F' since "6" is different from "06".

Given a string s containing only digits, return the number of ways to decode it.

Input: s = "12"
Output: 2
Explanation: "12" can be decoded as "AB" with mapping as 1->A and 2->B or "L" with mapping as 12->L.
Input: s = "226"
Output: 3
Explanation: "226" can be decoded as "BBF" with mapping as 2->B, 2->B and 6->F or "VF" with mapping as 22->V and 6->F or "BZ" with maping as 2->B and 26->Z.
Input: s = "06"
Output: 0
Explanation: "06" cannot be mapped to "F" because of the leading 0, if it was just 6 then it can be mapped to "F".

Contents

In this approach, we will generate all possible ways to decode the given input string. From every starting index of string we look at 1st indexed character and if it is valid, that is it should be a non zero character and 1st and 2nd characters together and if it is valid, that is it should be within the boundaries of two digit possible letters 10-26 (J-Z).

Say for example if the input string is "1212", you can see that from 1st digit '1' we can decode it to alphabet A or if you consider first two digits '12' we can decode it to L, similarly we will continue for other combinations of letters and decision tree for all possible combinations will look like below decision tree.

decode ways decision tree public class DecodeWays { /* brute force approach solution */ static int bruteForceApproach(String s) { if(s == null || s.startsWith("0")){ return 0; } if(s.length() == 0 || s.length() ==1) { return 1; } String first_char_remainder = s.substring(1); int result = bruteForceApproach(first_char_remainder); // recursively call for remaining substring after 1st character char char_1 = s.charAt(0); char char_2 = s.charAt(1); if(char_1 == '1' || (char_1=='2' && (char_2=='0' || char_2 == '1' || char_2 =='2' || char_2 =='3' || char_2 == '4' || char_2 == '5' || char_2 == '6'))) { String second_char_remainder = s.substring(2); result+= bruteForceApproach(second_char_remainder); } return result; } }
Complexity Analysis:

Time complexity: Above code runs in O(2n) time where n is the length of input string, this is because we are generating a decision tree with possible sub problems by taking possibilities with first character and first two characters.
Space complexity: O(n) since we calling the function recursively, this is the stack memory.

In the above implementation, we have created substrings using the indexes for easiness of understanding, but when we call the function recursively we could also pass starting index as parameter and avoid creating substrings.

From the above brute force approach, we can notice that there are some overlapping problems, such as sub problem with node 12 on the left side and the sub problem with node 12 on the right side are same. So, we could cache the results of sub problem and re-use that.

import java.util.HashMap; public class DecodeWays { /* DP memoize solution */ static int dpMemoizeApproach(String s) { HashMap<String, Integer> cache = new HashMap<>(); return dpMemoizeApproachRec(s, cache); } static int dpMemoizeApproachRec(String s, HashMap<String, Integer> cache) { if(s == null || s.startsWith("0")){ return 0; } if(s.length() == 0 || s.length() ==1) { return 1; } if(cache.containsKey(s)) { // re-use cached result if it exists return cache.get(s); } String first_char_remainder = s.substring(1); int result = bruteForceApproach(first_char_remainder); // recursively call for remaining substring after 1st character char char_1 = s.charAt(0); char char_2 = s.charAt(1); if(char_1 == '1' || (char_1=='2' && (char_2=='0' || char_2 == '1' || char_2 =='2' || char_2 =='3' || char_2 == '4' || char_2 == '5' || char_2 == '6'))) { String second_char_remainder = s.substring(2); result+= bruteForceApproach(second_char_remainder); } cache.put(s, result); // cache result return result; } }
Complexity Analysis:

Time complexity: Above code runs in O(n) time where n is the length of input string s.
Space complexity: O(n) for the cache we are maintaining and stack for recursive calls.

In this approach we will look at solving the problem using Dynamic programming using tabulation caching mechanism.
The time and memory complexity will be same as above solution using memoization, but we can notice a pattern to improve memory complexity to constant in later solution.

In this solution, we are going to create a temporary array to cache the possible ways to decode at current index, and for the next subsequent index possibilities, use the cache built so far and continue exploring number of ways to decode. The main intution in this solution is:

When we create cache array in below solution, we initialize array with n+1 size, because when we are at index i, first we are checking decoding is possible with single element at index i (that is 1-9), and then we are also checking whether decoding is possible with two characters (that is 10-26) when we construct two character elements, we are looking at previous element and adding current element. But, there is no previous element to 0th indexed element in the input string, so for our convinience we have created cache array with n+1 size and fill with element 1, which we will only only if there is a possibility of decoding with two characters.

Consider an example with input string "1212": DP tabulation example for input 1212

public class DecodeWays { /* DP tabulation approach */ static int dpTabulationApproach(String s) { if(s.charAt(0) == '0') { return 0; } int[] count = new int[s.length()+1]; count[0] = 1; // dummy value count[1] = 1; //decode possibilities for 0th index element from input string for(int i=2; i<=s.length(); i++) { int current = s.charAt(i-1); // though we are at i=2, we have to continue from 1st index of input string int prev = s.charAt(i-2); if(current > '0'){ // It does ascii comparison, but this is fine as ASCII values for 1 -9 will be greater than ASCII of 0 count[i] += count[i-1]; } if(prev =='1' || (prev == '2' && (current =='0' || current == '1' || current == '2' || current == '3' || current == '4' || current =='5' || current =='6'))) { count[i] += count[i-2]; } } // finally take the last element of DP tabulation array return count[s.length()]; } }
Complexity Analysis:

Time complexity: Above code runs in O(n) time where n is the length of input string s.
Space complexity: O(n) for the cache we are maintaining.

If you observe above solution DP using tabulation we are maintaining a temporary array with size n+1 size to cache the results, but when we are at index i we only need previous two values from the cache. So, in this approach we are going to make a small change to above program to keep track of only two previous values of cache and avoid temporary array that we have created.

public class DecodeWays { /* DP memory optimized */ static int doMemoryOptimized(String s) { if(s.charAt(0) == '0') { return 0; } int prev = 1; int current = 1; for(int i=2; i<=s.length(); i++) { int one_char = s.charAt(i-1); int two_char = s.charAt(i-2); int temp = 0; if(one_char > '0'){ temp += current; } if(two_char =='1' || (two_char == '2' && (one_char =='0' || one_char == '1' || one_char == '2' || one_char == '3' || one_char == '4' || one_char =='5' || one_char =='6'))) { temp += prev; } prev = current; current = temp; } // finally return current value return current; } }
Complexity Analysis:

Time complexity: Above code runs in O(n) time where n is the length of input string s.
Space complexity: O(1) since we are maintaining previous two values.

Above implementations source code can be found at GitHub link for Java code