leetcode91题,题解方法,做题思路分享
方法一 ,暴力递归+记忆化搜索
-
分析递归方法所需要的参数
- 根据题意可以知道,我们对当前字符串的切割方式有两种
- 取一位,那么该位就需要是’1’-'9’的字符,否则无效.
- 取两位,那么第一位应该是 ’ 1 ’ 或者 ’ 2 ',。当第一位是 ’ 1 '时,第二位可以是 ’ 0 ‘-’ 9 ’ 的任何数字。当第一位是 ’ 2 ’ 时,第二位应该 是 ’ 0 ’ - ’ 6 ’ 之间的数,否则无效。
- 分析完上面两个小步骤,我们能够感知到 递归函数里最多两次调用。
- 那么我们可以模拟从头开始取:
- 第一次有两种取法:1.取当前位置的字符。2.取当前位置和下一个位置的字符串。
- 第二次,第三次,第n次都这样取.
- 那么逻辑来了。第n次 我们是不是需要知道当前位置,从当前位置开始取。所以我们需要的参数就是当前位置了。
- 那么方法就该是 int dp(int startIndex,String s); 字符串s指代题目给的字符串。startIndex就是当前位置
- 根据题意可以知道,我们对当前字符串的切割方式有两种
-
分析递归方法的终止条件
- 不难看出startIndex==s.length()的时候就是终止条件,这个时候就该返回1 说明此分支可以分出正确的解码
- 其实一般终止条件都与递归函数的参数的变量有关。
-
分析递归过程。其实就是模拟 第n次怎么取的过程,下面进行模拟第n次
//有效终止返回1 if(startIndex==s.length()){ return 1; } //无效终止返回0 if(startIndex>s.length()){ return 0; } //第一位为0 说明 取一个和取两个都无效。终止返回0 if(s.charAt(startIndex)=='0'){ return 0; } //取一个 int dp1; dp1 = dp(startIndex + 1, s); //取两个 //第一位>2不能取 或者+2后越界不取 if(s.charAt(startIndex)-'0'>2||startIndex+2>s.length()){ return dp1; } //第一位 ==2 并且第二位大于6 不能取 if(s.charAt(startIndex)-'0'==2&&s.charAt(startIndex+1)-'0'>6){ return dp1; } int dp2; dp2=dp(startIndex+2,s); return dp1+dp2;
-
但是这样暴力递归,会导致重复遍历,很可能过不了leetcode的时间限制,所以要加上记忆化搜索。
-
定义dp数组,数组是几维的就看递归中变化的参数有几个。这里只有一个,所以 int[] dp=new int[s.length()+1].
-
就是在代码中判断咯
public int dp(int startIndex,String s){ if(startIndex==s.length()){ return 1; } if(startIndex>s.length()){ return 0; } if(s.charAt(startIndex)=='0'){ return 0; } //取一个 int dp1; //判断是否遍历过,遍历过之间取值,没遍历过就遍历,然后赋值给dp[startIndex+1] if(dp[startIndex+1]!=0){ dp1=dp[startIndex+1]; }else { dp1 = dp(startIndex + 1, s); dp[startIndex+1]=dp1; } //>2不能取 if(s.charAt(startIndex)-'0'>2||startIndex+2>s.length()){ return dp1; } if(s.charAt(startIndex)-'0'==2&&s.charAt(startIndex+1)-'0'>6){ return dp1; } //取两个 int dp2; //与上同理 if(dp[startIndex+2]!=0){ dp2=dp[startIndex+2]; }else { dp2=dp(startIndex+2,s); dp[startIndex+2]=dp2; } return dp1+dp2; }
-
-
就是递归调用。我们要从第一个位置开始,所以调用入口就是 dp(0,s);
方法二 动态规划,把dp[] 用循环实现,不再依靠递归
-
使用方法一执行时间只能打败 百分之几的人。而且这道题是求次数,并不需要打印所有的可能出来。满足动态规划。
-
依据b站左神(左程云)所说,动态规划都是由某一种 暴力递归改过来的。哈哈很明显。方法一就可以改成动态规划。我们只需要理解dp[]数组 从什么地方开始递推。然后赋上初始化,再采用循环递推就可以了,循环体就是递归中的 模拟部分。
-
下面给出动态规划的代码
class Solution { int[] dp; public int numDecodings(String s) { dp=new int[s.length()+1]; //赋上初始化 dp[s.length()]=1; for (int i=s.length()-1;i>=0;i--){ //循环体就是递归中的模拟部分,一模一样。 //就是将dp[]模拟出来 if(s.charAt(i)=='0'){ dp[i]=0; }else if(s.charAt(i)-'0'>2||i+2>s.length()){ dp[i]=dp[i+1]; }else if(s.charAt(i)-'0'==2&&s.charAt(i+1)-'0'>6){ dp[i]=dp[i+1]; }else { dp[i]=dp[i+1]+dp[i+2]; } } //最终返回的值,由递归的调入口决定是从0开始,所以我们要返回 dp[0]的值 return dp[0]; } }
-
当然这并不是最优方案,我们可以发现,第dp[i] 只跟dp[i+1]和dp[i+2]有关,所以我们还可以将内存优化,用三个常量来代替dp[]。
-
这里我们并没有严格按照动态规划的步骤来。没有去思考递归关系。只是把dp[] 从递归中解放出来了。
-
如果用动态规划的思路去思考。
- 划分为子问题。根据我们这道题,就可以这样看:以第i个位置开始的字符串有多少中切法,。
- 递推公式:以第i个位置开始的字符串有多少中切法==(以第i+1个位置开始的字符串有多少中切法)+(以第i+2个位置开始的字符串有多少中切法)
- 结束条件。
- 限制条件
-
当然还可以这样。
- 划分为子问题。根据我们这道题,就可以这样看:以第i个位置结束的字符串有多少中切法。
- 递推公式:以第i个位置结束的字符串有多少中切法==(以第i-1个位置结束的字符串有多少中切法)+(以第i-2个位置结束的字符串有多少中切法)本人感觉有点儿类似与跳楼梯的问题。
- 结束条件。
- 限制条件。
总结
动态规划就是由某一种暴力递归改出来的。我们在初学动态规划的时候,可以先不直接思考动态规划的递推公式啥的,我们可以通过常见的尝试模型去暴力递归然后优化,再去转动态规划。等我们熟练了,就可以比较快速在脑子里生成模型,然后直接用动态规划写出来。如果上述有误或者同学们有不懂的可以留言哟。