logo头像

学如逆水行舟,不进则退!!!

文章目录

动态规划破解加密你试过吗

一、题目描述

解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码

'A' -> 1
'B' -> 2
...
'Z' -> 26

要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,”11106” 可以映射为:

“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

二、思路分析

  • 此题考查的是动态规划的问题。所谓动态规划就是将大问题简化为小问题从而攻破小问题解决大问题。
  • 其实动态规划的解题思路是固定的,解题的思路总结下就是一句话:由大到小转移,从小开始解决

转移方程

  • 所谓转移方程就是由大到小转移。既然想通过大问题转化到小问题那么肯定得有一个过渡的依据才行
  • 一个动态规划是否可以完成主要取决在转移方程的设置上。如果理不清转移方程那么就无法实现动态规划。
  • 首先我们已1121这个数字进行解码为例。因为本题编码最大值是z–>26 。 26是两位数所以1121最终加入的编码最多是两位数

image-20210521140815915

  • 所以1121解码有多少种可能取决于112和11两个编码 。 如果令Fx表示第x位是存在的解码数的话我们可以得出如下公式

$$
f(x)=\left{\begin{array}{ll}
f(s-1) & a{x} \neq 0 \
+\
f(x-2) & a
{x+1} \neq 0 且 a{x+1} \cdot a{x} \leq 26
\end{array}\right.
$$

  • 如果上面公式看得有点突然的话我们可以这样理解。首先在编码是不可能出现连续的0的。因为编码表中不存在00这样的映射 且最大的映射数字是26所以才会有上面的条件
  • 当前数字不为零表示他肯定可以对应编码表中的一个数字。所以他的组合会包含前一字符串的解码数
  • 当前一数字不为零且与当前数字组成的两位数在26之内说明也可以在编码表中找到映射所以当前解码数又包含前一个的前一个解码数
  • 而且不同字符解码数是相互不影响的。所以才会 出现F(x) = F(x-1)+F(x-2) 。当然这是在一定条件内生效的

初始值

  • 由大问题转换为小问题。最终通过解决小问题实现大问题的最终解决。细化到什么地步才算是细化终止状态呢?肯定是细化到一个初始状态。
  • 在此问题上初始状态就是空字符。当编码是一个空是对应的解码自然就是空。而空就是我们的初始值
  • 在上面我们对Fx的定义是表示截止第x位存在的解码数。而我们所说的空自然对应的就是F0 。 而空解码后就是空。 所以F0=1

$$
f(0)=1
$$

  • f(0)表示的是空字符,其实在第一个字符时即f(1)=1也是初始值。但是我们根据题意或者说根据测试用例来说f(1)是一个变量。为什么这么说呢?
  • 因为在测试用例中存在0或者06等这些数据。这些数据因为0是无法匹配的。而在映射表中只能已0结尾的数没有已0开头的数。所以这些数据f(1)=0
  • 所以f(1)需要参数转移方程的计算。但是f(1)对应的前两个是f(-1) , 为了避免数组越界我们需要在加上条件限制

从小打到计算

  • 转移方程和初始值确定好之后,剩下的就是我们开始迭代计算了。迭代的过程就是从小到大的执行的过程

三、AC 代码

//特殊快速处理
if (0==s.length()) {
    return 0;
}
//构建一个用于存放前面产生的解码数 ; 用于转移方程用
int[] countArr = new int[s.length()+1];
//初始值
countArr[0] = 1;
for (int i = 1; i <= s.length(); i++) {
    //chart转int 的方式   char-'0'
    int sint = s.charAt(i-1)-'0';
    if (sint != 0) {
        countArr[i] += countArr[i - 1];
    }
    //需要过滤第一个元素
    if(i>1&&s.charAt(i-2)!='0'&&(s.charAt(i-2)-'0')*10+(s.charAt(i-1)-'0')<=26){
        countArr[i] += countArr[i - 2];

    }
}
return countArr[s.length()];

image-20210521161032014

  • 执行速度依旧是那么顺眼。内存消耗也还能接受 。 但是我们自信想想针对此题我们是没有必要开辟一个数字来存储每一个节点的解码数的。我们用到的最多是前两个状态。所以我们可以专门开辟两个变量存储。在开辟一个变量存储当前解码数。
public int numDecodings(String s) {
    if (0==s.length()) {
        return 0;
    }
    int pre =1;
    int prepre = 0;
    int current = 0;
    for (int i = 1; i <= s.length(); i++) {
        current = 0;
        int sint = s.charAt(i-1)-'0';
        if (sint != 0) {
            current = pre;
        }
        if(i>1&&s.charAt(i-2)!='0'&&(s.charAt(i-2)-'0')*10+(s.charAt(i-1)-'0')<=26){
            current += prepre;
        }
        prepre = pre;
        pre = current;
    }
    return pre;
}

image-20210521161216513

四、总结

  • 动态规划是算法中绕不开的坎。想要在算法上有所收获就必须彻底掌握动态规划。
  • 说实话自己对动态规划掌握也不熟悉。每次在高难度的动态规划中很难准确的理清转移方程。
  • 革命尚未成功,同志仍需努力。

收藏

上一篇
坚持原创技术分享,您的支持将鼓励我继续创作!

评论系统未开启,无法评论!