A-Z
is being encoded to numbers using the following mapping:
'A' -> 1 'B' -> 2 ... 'Z' -> 26Given an encoded message containing digits, determine the total number of ways to decode it.
For example,
Given encoded message
"12"
,
it could be decoded as "AB"
(1 2) or "L"
(12).
The number of ways decoding
"12"
is 2.Naive Way: I first came up with DFS, which get TLE. By expanding the DFS approach, I see redundancy lies in the reuse of high index calls, like (S, 4) will be called 4 times in example "1224".
So there should be a corresponding DP approach.
public class Solution {
public int numDecodings(String s) {
return dfs(s, 0);
}
private int dfs(String s, int index){
// base case
if(index >= s.length()) return 1;
// recursion
int count = 0;
if(index+1 < s.length() &&(s.charAt(index) == '1' || s.charAt(index) == '2' && s.charAt(index+1) <= '6'))
count += dfs(s, index+2);
if(s.charAt(index) != '0')
count += dfs(s, index+1);
return count;
}
}
The logic of DP can be concluded as
opt[i,j] // the # of ways to decode string s[i....j]
// base case
opt[0,0] = 1 , opt[i,j] = 0 for i > j
// iteration
opt[i,j] = s[j]!='0' ?opt[i,j-1]:0 + (s[j-1] == '1' || s[j-1] == '2' && s[j] <= '6')?opt[i,j-2]:0;
The below is the implementation, time complexity O(n^2) and space O(n^2) required.
public class Solution {
public int numDecodings(String s) {
// edge case
if(s.length()==0) return 0;
// DP
int opt[][] = new int[s.length()][s.length()+1];
// base case
opt[0][0] = 1;
// iteration
for(int i = 1;i <= s.length();i++)
for(int j = 0;j+i <= s.length();j++)
opt[j][j+i] = ((s.charAt(j+i-1)!='0')?opt[j][j+i-1]:0) + ((i >=2 && (s.charAt(j+i-2)=='1' || s.charAt(j+i-2)=='2' && s.charAt(j+i-1)<='6'))?opt[j][j+i-2]:0);
return opt[0][s.length()];
}
}
In the above code, I found out that the step is totally useless, opt[3][4] will never be used to compute opt[0][4]. So the final solution should be O(n) time and space.
public class Solution {
public int numDecodings(String s) {
// edge case
if(s.length()==0) return 0;
// DP
int opt[] = new int[s.length()+1];
// base case
opt[0] = 1;
// iteration
for(int j = 1;j <= s.length();j++)
opt[j] = ((s.charAt(j-1)!='0')?opt[j-1]:0) + ((j >=2 && (s.charAt(j-2)=='1' || s.charAt(j-2)=='2' && s.charAt(j-1)<='6'))?opt[j-2]:0);
return opt[s.length()];
}
}
It's really bad to write an algorithm without carefully think about dimension. The problem is a one -dimension problem, while the middle result may be useful. But in writing the algorithm on can clearly see that middle result is useless.
Improved Way: As usual, a 2D space can always be reduced to 1D when current result only rely on previous row/column. For this problem, current result only relies on previous two steps. We can reduced the space use to O(1).
public class Solution {
public int numDecodings(String s) {
// edge case
if(s.length()==0) return 0;
// DP
int father = 1, grandpa = 1;
// base case
int opt = 1;
// iteration
for(int j = 1;j <= s.length();j++){
opt = ((s.charAt(j-1)!='0')?father:0) + ((j >=2 && (s.charAt(j-2)=='1' || s.charAt(j-2)=='2' && s.charAt(j-1)<='6'))?grandpa:0);
grandpa = father;
father = opt;
}
return opt;
}
}
While, it's not the end, I finally find someone's post with an improved DFS approach. It's from
public int numDecodings(String s) {
if(s == null || s.length() == 0) return 0;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map. put(s.length(), 1);
return dfs(s, 0, map);
}
private int dfs(String s, int index, Map<Integer, Integer> map){
// check path memory
if(map.containsKey(index)) return map.get(index);
// recursion
int count = 0;
if(index+1 < s.length() &&(s.charAt(index) == '1' || s.charAt(index) == '2' && s.charAt(index+1) <= '6'))
count += dfs(s, index+2, map);
if(s.charAt(index) != 0)
count += dfs(s, index+1, map);
map.put(index, count);
return count;
}
No comments:
Post a Comment