给定数字类型的字符串,转化为字母类型的字符串,转化的方法数是多少?
提示:暴力递归,Facebook考题
题目
给定数字类型的字符串s,转化为字母类型的字符串,转化的方法数是多少?
转化规则是:
1–26对应A–Z字符
一、审题
示例:比如:
111=s,有多少种转化方法,变成字母字符串?
1 1 1=AAA【每个1单独变】
1 11=AK【11组合】
11 1=KA
111能转化吗?不能,因为超过26了
数字只能组合为1位或者2位
二、解题
咱们从左往右决定i位置的数字,是单独转化呢?还是联合i+1凑成对来转化?
下面是转化规则:
定义暴力递归f(str,i):目前str[0–i-1]范围内的已经转化好了,请问你i–N-1范围上的字符继续转化,有多少种方案?
(1)遇到0,不好意思,整个方案不行,转化方法为0;
(2)遇到i来到了N位置呢?越界了,说明刚刚好,整个方案可以搞定。返回1
(3)来到任意位置i,其实i可以单独走,也可以配合i+1组合成2位数字,当然不能再用i+2组合成3位,因为最大26哇
这里面就分为几种情况:
1)i位置是1,那么可以单独转化A【下一次直接去探索i+1】,i+1还不越界的话,i和i+1可以组合为10–19【下一次直接去探索i+2】
所以结果可以是这俩的和:f(i+1)+f(i+2),然后返回;
2)i位置是2,那么可以单独转化B【下一次直接去探索i+1】,i+1还不越界的话,i和i+1可以组合为20–29,注意,只能是20–26哦,其他非法的,所以限定i+1必须在0–6之间【下一次直接去探索i+2】
所以结果可以是这俩的和:f(i+1)+f(i+2),然后返回;
3)i位置是3–9,没啥可以说,他们只能单独转化为C–I,没法组合出30++来,只能是1–26【下一次直接去探索i+1】
所以结果只能是f(i+1)
这就是我们的递归,主函数怎么调用?
直接调用f(str,0),让你求str的0–N-1位置上有多少种转化方案,就是原题的意思
手撕代码:
//复习:
//目前str[0--i-1]范围内的已经转化好了,请问你i--N-1范围上的字符继续转化,有多少种方案?
public static int f(char[] str, int i){
int N = str.length;
//(2)遇到i成功地来到了N位置呢?越界了,说明刚刚好,整个方案可以搞定。返回1
if (i == N) return 1;//这个条件得在前面,越界了就OK
//(1)遇到0,不好意思,整个方案不行,转化方法为0;
if (str[i] == '0') return 0;
//(3)来到任意位置i,其实i可以单独走,也可以配合i+1组合成2位数字,当然不能再用i+2组合成3位,因为最大26哇
//这里面就分为几种情况:
int ans = 0;
//1)i位置是1,那么可以单独转化A【下一次直接去探索i+1】,i+1还不越界的话,i和i+1可以组合为10--19【下一次直接去探索i+2】
//所以结果可以是这俩的和:**f(i+1)+f(i+2)**,然后返回;
if (str[i] == '1'){
ans += f(str, i + 1);//1独立转化,当然你可以存下结果
if (i + 1 < N) ans += f(str, i +2);//i+1还没越界,可以考虑组合1x
}
//2)i位置是2,那么可以单独转化B【下一次直接去探索i+1】,i+1还不越界的话,i和i+1可以组合为20--29,注意,只能是20--26哦,其他非法的,所以限定i+1必须在0--6之间【下一次直接去探索i+2】
//所以结果可以是这俩的和:**f(i+1)+f(i+2)**,然后返回;
else if (str[i] == '2') {
ans += f(str, i + 1);//1独立转化,当然你可以存下结果
if (i + 1 < N && str[i + 1] >= '0' && str[i + 1] <= '6')
ans += f(str, i + 2);//i+1还没越界,可以考虑组合20--26
}
//3)i位置是3--9,没啥可以说,他们只能单独转化为C--I,没法组合出30++来,只能是1--26【下一次直接去探索i+1】
//所以结果只能是**f(i+1)**
else ans += f(str,i + 1);
return ans;
}
//直接调用f(str,0),让你求str的0--N-1位置上有多少种转化方案,就是原题的意思
public static int howManyWaysConvert(String s){
if (s.compareTo("") == 0 || s.length() == 0) return 0;
char[] str = s.toCharArray();
return f(str, 0);
}
public static void test(){
String s = "111";
System.out.println(numToCharacter1(s));
System.out.println(howManyWaysConvert(s));
}
3
3
暴力递归转动态规划代码
这里f就一个变量i,定义为:探索str的i–N-1范围上有多少种转化方案?
主函数是f(str,0),从0–N-1位置,最后不断分,直到i==N-1时,拿到结果,一层一层返回,最求了f(0)
干脆咱们用一个数组dp去存这个结果,dp长度为N+1,因为i=N时,返回结果是1
dp[i]定义为为:探索str的i–N-1范围上有多少种转化方案
咱最终要求的就是dp[0],要f不断递归去找,到终止条件:dp[N]=1,然后返回来地推dp[0]
这大概就是动态规划的意思
代码咋写呢?完全根据上面的暴力递归写代码:
//复习:
//dp[i]定义为为:**探索str的i--N-1范围上有多少种转化方案**
public static int howManyWaysConvertDP(String s){
if (s.compareTo("") == 0 || s.length() == 0) return 0;
int N = s.length();
//一个变量i,根据f(str,i)来改编
int[] dp = new int[N +1];
char[] str = s.toCharArray();
//(2)遇到i成功地来到了N位置呢?越界了,说明刚刚好,整个方案可以搞定。返回1
dp[N] = 1;
//然后填表,从右往左填表,递归dp[0]
for (int i = N - 1; i >= 0; i--) {
//(1)遇到0,不好意思,整个方案不行,转化方法为0;
if (str[i] == '0') dp[i] = 0;
//(3)来到任意位置i,其实i可以单独走,也可以配合i+1组合成2位数字,当然不能再用i+2组合成3位,因为最大26哇
//这里面就分为几种情况:
int ans = 0;
//1)i位置是1,那么可以单独转化A【下一次直接去探索i+1】,i+1还不越界的话,i和i+1可以组合为10--19【下一次直接去探索i+2】
//所以结果可以是这俩的和:**f(i+1)+f(i+2)**,然后返回;
if (str[i] == '1'){
ans += dp[i + 1];//1独立转化,当然你可以存下结果
if (i + 1 < N) ans += dp[i +2];//i+1还没越界,可以考虑组合1x
}
//2)i位置是2,那么可以单独转化B【下一次直接去探索i+1】,i+1还不越界的话,i和i+1可以组合为20--29,注意,只能是20--26哦,其他非法的,所以限定i+1必须在0--6之间【下一次直接去探索i+2】
//所以结果可以是这俩的和:**f(i+1)+f(i+2)**,然后返回;
else if (str[i] == '2') {
ans += dp[i + 1];//1独立转化,当然你可以存下结果
if (i + 1 < N && str[i + 1] >= '0' && str[i + 1] <= '6')
ans += dp[i + 2];//i+1还没越界,可以考虑组合20--26
}
//3)i位置是3--9,没啥可以说,他们只能单独转化为C--I,没法组合出30++来,只能是1--26【下一次直接去探索i+1】
//所以结果只能是**f(i+1)**
else ans += dp[i + 1];
dp[i] = ans;//把可能的情况赋值给它
}
return dp[0];//这就是我们要的结果,str0--N-1范围上的可能转化结果。
}
public static void test2(){
String s = "111";
System.out.println(numToCharacter2(s));
System.out.println(howManyWaysConvertDP(s));
}
public static void main(String[] args) {
// test();
test2();
}
3
3
这么做的好处是啥呢?
比如你求f(9),你每次都要重复去求f(10),f(11),……,f(N-1)
如果当初f(10)已经通过dp[10]保存好了,就不用再次递归求f(11),……,f(N-1)了
这样节省了大量的递归时间。
这就是动态规划的本意。
总结
提示:重要经验:
1)从左往右的尝试,就是看i位置的情况如何,来做决定,决定下一次要去i+1还是i+2,以后经常又题目会这么分析。
2)暴力递归可以转化为动态规划的代码,所谓动态规划,就是在递归求规模更小时,直接保存结果,然后在更大的规模调用递归时直接获取下面的结果,不至于反复递归求到底。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。