Leetcode-91. Decode Ways

題目描述(中等难度)

在这里插入图片描述
每个数字对应一个字母,给一串数字,问有几种解码方式。例如 226 可以有三种,2|2|6,22|6,2|26。

解法一 递归

很容易想到递归去解决,将大问题化作小问题。

比如 102213。

对于第一个字母我们有两种划分方式。

1|02213 和 10|2213

所以,如果我们分别知道了上边划分的右半部分 02213 的解码方式是 ans1 种,2213 的解码方式是 ans2 种,那么整体 102213 的解码方式就是 ans1 + ans2 种。
在这里插入图片描述
对于本题的递归流程,可以参考下图。
在这里插入图片描述

写出递归函数也就是要处理好递归的3个主要的点:

  • a)出口条件,即递归“什么时候结束”,这个通常在递归函数的开始就写好;
  • b) 如何由"情况 n" 变化到"情况 n+1", 也就是非出口情况,也就是一般情况——"正在"递归中的情况;
  • c) 初始条件,也就是这个递归调用以什么样的初始条件开始可以说。

上述a,b,c三个条件组成了我们的递归函数;解决好上述3点,也就很容易地写出一个递归函数;剩下的就是去学习学习“数学归纳法”;不需要你称为归纳法专家,但只需要认证体会它的思路,对于你理解和创造递归函数有很大帮助。

public class Decode_Ways {
	
	public static int numDecodings(String s) {
	    return getAns(s, 0);
	}

	private static int getAns(String s, int start) {
	    //划分到了最后返回 1
	    if (start == s.length()) {
	        return 1;
	    }
	    //开头是 0,0 不对应任何字母,直接返回 0
	    if (s.charAt(start) == '0') {
	        return 0;
	    }
	    //得到第一种的划分的解码方式
	    int ans1 = getAns(s, start + 1);
	    int ans2 = 0;
	    //判断前两个数字是不是小于等于 26if (start < s.length() - 1) {
	        int ten = (s.charAt(start) - '0') * 10;
	        int one = s.charAt(start + 1) - '0';
	        if (ten + one <= 26) {
	            ans2 = getAns(s, start + 2);
	        }
	    }
	    return ans1 + ans2;
	}
	public static void main(String args[]) {
		String s="226";
		int ans=numDecodings(s);
		System.out.println(ans);	
	}
}

解法二 递归 memoization

解法一的递归中,走完左子树,再走右子树会把一些已经算过的结果重新算,所以我们可以用 memoization 技术,就是算出一个结果很就保存,第二次算这个的时候直接拿出来就可以了

public class Decode_Ways3 {
	
	public static int numDecodings(String s) {
		HashMap<Integer,Integer>memoization=new HashMap<>();
	    return getAns(s, 0,memoization);
	}

	private static int getAns(String s, int start,HashMap<Integer,Integer>memoization) {
	    //划分到了最后返回 1
	    if (start == s.length()) return 1;
	    
	    //开头是 0,0 不对应任何字母,直接返回 0
	    if (s.charAt(start) == '0') return 0;
	    
	    // 判断之前是否计算过
	    int m=memoization.getOrDefault(start, -1);
	    if(m!=-1) {
	    	return m;
	    }
	    //得到第一种的划分的解码方式
	    int ans1 = getAns(s, start + 1,memoization);
	    int ans2 = 0;
	    //判断前两个数字是不是小于等于 26if (start < s.length() - 1) {
	        int ten = (s.charAt(start) - '0') * 10;
	        int one = s.charAt(start + 1) - '0';
	        if (ten + one <= 26) {
	            ans2 = getAns(s, start + 2,memoization);
	        }
	    }
	    return ans1 + ans2;
	}
}

解法三 动态规划

同样的,递归就是压栈压栈压栈,出栈出栈出栈的过程,我们可以利用动态规划的思想,省略压栈的过程,直接从 bottom 到 top。

用一个 dp 数组, dp [ i ] 代表字符串 s [ i, s.len-1 ],也就是 s 从 i 开始到结尾的字符串的解码方式。

这样和递归完全一样的递推式。

如果 s [ i ] 和 s [ i + 1 ] 组成的数字小于等于 26,那么

dp [ i ] = dp[ i + 1 ] + dp [ i + 2 ]

在这里插入图片描述
接下来就是,动态规划的空间优化了,例如5题,10题,53题,72题等等都是同样的思路。都是注意到一个特点,当更新到 dp [ i ] 的时候,我们只用到 dp [ i + 1] 和 dp [ i + 2],之后的数据就没有用了。所以我们不需要 dp 开 len + 1 的空间。

简单的做法,我们只申请 3 个空间,然后把 dp 的下标对 3 求余就够了。

public int numDecodings4(String s) {
    int len = s.length();
    int[] dp = new int[3];
    dp[len % 3] = 1;
    if (s.charAt(len - 1) != '0') {
        dp[(len - 1) % 3] = 1;
    }
    for (int i = len - 2; i >= 0; i--) {
        if (s.charAt(i) == '0') {
            dp[i % 3] = 0; //这里很重要,因为空间复用了,不要忘记归零
            continue;
        }
        int ans1 = dp[(i + 1) % 3];
        int ans2 = 0;
        int ten = (s.charAt(i) - '0') * 10;
        int one = s.charAt(i + 1) - '0';
        if (ten + one <= 26) {
            ans2 = dp[(i + 2) % 3];
        }
        dp[i % 3] = ans1 + ans2;

    }
    return dp[0];
}

总结

一般可以使用递归求解的题目都可以使用动态规划,并且之前做过的很多题中也都是这样思路。

参考文献

1.https://www.youtube.com/watch?v=OjEHST4SXfE&t=442s
2.https://zhuanlan.zhihu.com/p/70382397
3.https://www.zhihu.com/question/20096035
4.https://leetcode-cn.com/problems/decode-ways/solution/di-gui-dong-tai-gui-hua-kong-jian-ya-suo-man-man-d/
5.https://www.processon.com/diagraming/5aab2fd6e4b05a5cc2fcd1c7

发布了168 篇原创文章 · 获赞 49 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_35770067/article/details/104877972