需要帮助来了解这种动态编程解决方案

时间:2019-03-07 22:28:55

标签: algorithm dynamic-programming

所以要问的问题是:

使用以下映射将包含A-Z字母的消息编码为数字:

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

给出一个仅包含数字的非空字符串,确定解码它的总数。

示例1:

Input: "12"
Output: 2
Explanation: It could be decoded as "AB" (1 2) or "L" (12).

示例2:

Input: "226"
Output: 3
Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).

我非常无效率地解决了该问题,并且正在寻找其他解决方案,并发现动态编程是解决此问题的好方法。由于DP对我来说是新手,所以我一直在阅读有关它的信息,现在回到我看到的解决方案中,我试图了解这个人所采用的自下而上方法背后的逻辑。

function numDecodings(s) {
  if (s.length === 0) return 0;

  const N = s.length;
  const dp = Array(N+1).fill(0);

  dp[0] = 1;
  dp[1] = s[0] === '0' ? 0 : 1;

  for (let i = 2; i <= N; i++) {
    if (s[i-1] !== '0') {
      dp[i] += dp[i-1];
    }
    if (s[i-2] === '1' || s[i-2] === '2' && s[i-1] <= '6') {
      dp[i] += dp[i-2];
    }
  }

  return dp[N];
}

1 个答案:

答案 0 :(得分:0)

首先,让我们理顺一些术语:

  • 有“自上而下”和“自下而上”的方法。 “自下而上”不是一个有用的术语。
  • DP是一种“自下而上”的方法,因为每种解决方案都基于较小的和较新的解决方案。

该代码具有一个“ memo”数组dp。您可能会在阅读物中看到“记忆化”一词。这意味着,当我们首先计算特定子问题的解决方案时,我们将为其做一个备忘(记住解决方案),并由参数索引。此后,无论何时需要解决方案,我们都将简单地查找它,而不用重新计算它。

在字符串的每个位置,我们都记住到目前为止编码字符串有多少种方法,然后计算将当前字符添加到该前缀时总共有多少种方法。

非常简短:

  • 如果当前字符不为0,那么我们可以用一种方法继续任何先前的字符串:将其作为对字母a-i进行编码。在这种情况下,到目前为止,每种编码仍然有效,因此我们继续使用该计数:dp[i] += dp[i-1]
  • 如果以前的字符和当前的字符形成合法的编码,那么我们也可以将它们作为2位数的编码(字母jz),然后从该2个字符的代码之前继续计数:dp[i] += dp[i-1]

这就是算法的全部内容。请注意,这不会处理所有可能的数字序列:如果代码到达没有任何可能的延续的点,它将仅允许dp[i]保持为0,然后继续而不发出消息。例如,给定输入12000226,该算法将确定三种编码12的方式,将其扩展为at包括120,然后在遇到下一个时重置两个零。然后从下一个2开始,将找到3种方式编码字符串的其余部分,并返回3作为结果。