题目:
A message containing letters from A-Z is being encoded to numbers using the following mapping:
‘A‘ -> 1
‘B‘ -> 2
...
‘Z‘ -> 26
Given 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.
翻译:
一条消息有A-Z这些字母组成,但是消息被编码为数字了,按照以下方式:
‘A‘ -> 1
‘B‘ -> 2
...
‘Z‘ -> 26
给你一条编码后的消息,算出可能的解码方式的数量。
例如,给你一个消息”12”,它可以被解析为“AB”(1,2)或者“L”(12),所以这条消息解码方式的数量是2。
分析:
以下有2种方式可以实现,其中方式一在LeetCode的耗时是8 ms,方式二的是 2 ms。
方式一:假设字符串无限长,例如ABXXXXXXX…,我们只关注前2位。
1. 当A单独解析时,可能的解析方式有numDecodings(BXXXXXXX…)种。
2. 当A和B组合解析时,可能的解析方式有numDecodings(XXXXXXX…)种。
最后的结果,是上述2种情况的和。
方式二:
1. 首先将字符串中的数字进行校验、转换整理。我们将数字分为4类:
1)拆也可以不拆也可以,但不能打头,例如18的8、26的6,用Y替换
2)必须拆,并且不能打头,例如28的8,直接用X替换
3)拆也可以不拆也可以,但可以打头,例如11的1,不修改
4)必须与前一个数字合并,例如10的0,将前一个数字和本数字替换为XX
上述X表示隔断,即X前后的数字分开计算,再乘起来
上述Y表示隔断,与X类似,但是Y参与前面的数字计算。
2. 经过步骤一,字符串中的字符只剩下1、2、X、Y了。其中X、Y作为隔断,只要统计被隔断分割开来的每组数字的结果,然后把它们相乘起来即可。而如何计算每组数字的结果,通过观察发现,每组数字的结果与输入数字的个数有关,并且符合斐波那契数列。
Java版代码(方式1):
public class Solution091A {
private static Map<String, Integer> resMap = new HashMap<>();
public int numDecodings(String s) {
char[] chars = s.toCharArray();
int len = chars.length;
if (len == 0) return 0;
Integer res1 = 0;
Integer res2 = 0;
if (len >= 1) {
char ch = chars[0];
if (ch == ‘0‘) {
return 0;
} else {
if (len == 1) {
res1 = 1;
} else {
String substring = s.substring(1);
res1 = resMap.get(substring);
if (res1 == null) {
res1 = numDecodings(substring);
resMap.put(substring, res1);
}
}
}
if (len >= 2) {
if (ch == ‘1‘) {
if (len == 2) {
res2 = 1;
} else {
res2 = numDecodings(s.substring(2));
}
} else if (ch == ‘2‘) {
if (chars[1] >= ‘0‘ && chars[1] <= ‘6‘) {
if (len == 2) {
res2 = 1;
} else {
String substring = s.substring(2);
res2 = resMap.get(substring);
if (res2 == null) {
res2 = numDecodings(substring);
resMap.put(substring, res2);
}
}
}
}
}
}
return res1 + res2;
}
}
Java版代码(方式2):
public class Solution091B {
private static Map<Integer, Integer> fMap = new HashMap<>();
public int numDecodings(String input) {
if (input == null || input.length() == 0) {
return 0;
}
char[] chars = input.toCharArray();
if (!clean(chars)) return 0;
return cal(chars);
}
/**
* 将字符串中的数字进行校验、转换整理
*
* @param chars
* @return 校验结果是否合法
* @author lnho
*/
private boolean clean(char[] chars) {
for (int i = 0; i < chars.length; i++) {
char current = chars[i];
if (current == ‘0‘ || (current >= ‘3‘ && current <= ‘9‘)) {
if (i == 0) {
if (current == ‘0‘) {
return false;
} else {
//为第一位数字,符合情况(2)
chars[i] = ‘X‘;
continue;
}
}
char previous = chars[i - 1];
if (previous != ‘1‘ && previous != ‘2‘) {
if (current == ‘0‘) {
return false;
} else {
//前面为隔断(X,Y),符合情况(2)
chars[i] = ‘X‘;
continue;
}
}
if (previous == ‘2‘ && current > ‘6‘) {
//符合情况(2)
chars[i] = ‘X‘;
continue;
}
if (current == ‘0‘) {
//符合情况(4)
chars[i - 1] = ‘X‘;
chars[i] = ‘X‘;
} else {
//符合情况(1)
chars[i] = ‘Y‘;
}
}
//符合情况(3)
}
return true;
}
/**
* 计算结果
*
* @param chars 经过上一个步骤,入参的字符数组中只包括1,2,X,Y了
* @return
* @author lnho
*/
private int cal(char[] chars) {
int count = 0;
int sum = 1;
for (char current : chars) {
if (current == ‘1‘ || current == ‘2‘) {
count++;
} else if (current == ‘Y‘) {
sum *= fibonacci(count + 1);
count = 0;
} else {
if (count != 0) {
sum *= fibonacci(count);
count = 0;
}
}
}
if (count != 0) {
sum *= fibonacci(count);
}
return sum;
}
/**
* 获取斐波那契数值
*
* @param n
* @return
* @author lnho
*/
private int fibonacci(int n) {
if (n == 1 || n == 2) {
return n;
}
Integer fn = fMap.get(n);
if (fn == null) {
fn = fibonacci(n - 1) + fibonacci(n - 2);
fMap.put(n, fn);
}
return fn;
}
}