leetcode91题,题解方法,做题思路分享

leetcode91题,题解方法,做题思路分享

题目内容

方法一 ,暴力递归+记忆化搜索
  1. 分析递归方法所需要的参数

    • 根据题意可以知道,我们对当前字符串的切割方式有两种
      1. 取一位,那么该位就需要是’1’-'9’的字符,否则无效.
      2. 取两位,那么第一位应该是 ’ 1 ’ 或者 ’ 2 ',。当第一位是 ’ 1 '时,第二位可以是 ’ 0 ‘-’ 9 ’ 的任何数字。当第一位是 ’ 2 ’ 时,第二位应该 是 ’ 0 ’ - ’ 6 ’ 之间的数,否则无效。
      3. 分析完上面两个小步骤,我们能够感知到 递归函数里最多两次调用。
    • 那么我们可以模拟从头开始取:
      1. 第一次有两种取法:1.取当前位置的字符。2.取当前位置和下一个位置的字符串。
      2. 第二次,第三次,第n次都这样取.
      3. 那么逻辑来了。第n次 我们是不是需要知道当前位置,从当前位置开始取。所以我们需要的参数就是当前位置了。
    • 那么方法就该是 int dp(int startIndex,String s); 字符串s指代题目给的字符串。startIndex就是当前位置
  2. 分析递归方法的终止条件

    • 不难看出startIndex==s.length()的时候就是终止条件,这个时候就该返回1 说明此分支可以分出正确的解码
    • 其实一般终止条件都与递归函数的参数的变量有关。
  3. 分析递归过程。其实就是模拟 第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;
    
    
  4. 但是这样暴力递归,会导致重复遍历,很可能过不了leetcode的时间限制,所以要加上记忆化搜索。

    1. 定义dp数组,数组是几维的就看递归中变化的参数有几个。这里只有一个,所以 int[] dp=new int[s.length()+1].

    2. 就是在代码中判断咯

      		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;
               }
      
  5. 就是递归调用。我们要从第一个位置开始,所以调用入口就是 dp(0,s);

方法二 动态规划,把dp[] 用循环实现,不再依靠递归
  1. 使用方法一执行时间只能打败 百分之几的人。而且这道题是求次数,并不需要打印所有的可能出来。满足动态规划。

  2. 依据b站左神(左程云)所说,动态规划都是由某一种 暴力递归改过来的。哈哈很明显。方法一就可以改成动态规划。我们只需要理解dp[]数组 从什么地方开始递推。然后赋上初始化,再采用循环递推就可以了,循环体就是递归中的 模拟部分。

  3. 下面给出动态规划的代码

        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];
            }
    
    
        }
    
  4. 当然这并不是最优方案,我们可以发现,第dp[i] 只跟dp[i+1]和dp[i+2]有关,所以我们还可以将内存优化,用三个常量来代替dp[]。

  5. 这里我们并没有严格按照动态规划的步骤来。没有去思考递归关系。只是把dp[] 从递归中解放出来了。

  6. 如果用动态规划的思路去思考。

    1. 划分为子问题。根据我们这道题,就可以这样看:以第i个位置开始的字符串有多少中切法,。
    2. 递推公式:以第i个位置开始的字符串有多少中切法==(以第i+1个位置开始的字符串有多少中切法)+(以第i+2个位置开始的字符串有多少中切法)
    3. 结束条件。
    4. 限制条件
  7. 当然还可以这样。

    1. 划分为子问题。根据我们这道题,就可以这样看:以第i个位置结束的字符串有多少中切法。
    2. 递推公式:以第i个位置结束的字符串有多少中切法==(以第i-1个位置结束的字符串有多少中切法)+(以第i-2个位置结束的字符串有多少中切法)本人感觉有点儿类似与跳楼梯的问题。
    3. 结束条件。
    4. 限制条件。
总结

动态规划就是由某一种暴力递归改出来的。我们在初学动态规划的时候,可以先不直接思考动态规划的递推公式啥的,我们可以通过常见的尝试模型去暴力递归然后优化,再去转动态规划。等我们熟练了,就可以比较快速在脑子里生成模型,然后直接用动态规划写出来。如果上述有误或者同学们有不懂的可以留言哟。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值