返回

挖掘「数位 DP」的潜力:动态规划算法的巧妙运用

后端

当然可以,以下是动态规划算法解析:

动态规划算法的魅力

动态规划是一种解决复杂问题的经典算法范式。其核心思想在于将问题分解成一系列子问题,并通过子问题的最优解来递推得到原问题的最优解。这种「从下而上」的思路使得动态规划算法在解决许多现实问题时表现出优异的性能。

「数位 DP」的独特之处

「数位 DP」是动态规划算法的一个分支,它专门解决与数字有关的问题。在「数位 DP」中,我们将数字视为由个位、十位、百位等组成的「数位」,并通过对这些「数位」进行操作来解决问题。这种思路使得「数位 DP」在处理数字问题时往往能展现出令人惊叹的效率和简洁性。

LeetCode 1012:至少有 1 位重复的数字

LeetCode 1012 是一个「困难」难度的题目,题干如下:

给定正整数 n,返回在 [1, n] 范围内所有数字中,至少有一位重复的数字的个数。

乍一看,这道题似乎很难下手。但是,如果我们换个思路,将数字分解成「数位」,并利用「数位 DP」的思想来解决问题,那么一切就变得豁然开朗了。

算法解析

我们首先定义一个状态 dp[i][j][k],其中:

  • i 表示当前考虑的数字的位数。
  • j 表示当前考虑的数字的最高位数字。
  • k 表示当前考虑的数字是否至少有一位重复的数字。

然后,我们就可以根据以下递推关系来计算 dp[i][j][k]

  • 如果 i = 1,那么 dp[i][j][k] = 1,因为任何一位数字都满足题目要求。
  • 如果 i > 1,那么 dp[i][j][k] 可以分解成两种情况:
    • 如果当前考虑的数字的最高位数字 j 已经出现过,那么 dp[i][j][k] 等于当前考虑的数字的最高位数字 j 之前的数字的个数乘以当前考虑的数字的最高位数字 j 的个数,即 dp[i - 1][j][1] * j
    • 如果当前考虑的数字的最高位数字 j 没有出现过,那么 dp[i][j][k] 等于当前考虑的数字的最高位数字 j 之前的数字的个数乘以当前考虑的数字的最高位数字 j 的个数,再乘以不包含当前考虑的数字的最高位数字 j 的所有数字的个数,即 dp[i - 1][j][1] * j * dp[i - 1][j - 1][0]

最后,我们只需要计算 dp[log_{10}(n) + 1][0][1] 的值,就可以得到在 [1, n] 范围内所有数字中,至少有一位重复的数字的个数了。

代码实现

def numDupDigitsAtMostN(n):
    """
    :type n: int
    :rtype: int
    """
    # 定义状态 dp[i][j][k]
    dp = [[[0 for _ in range(2)] for _ in range(10)] for _ in range(10)]

    # 初始化
    for i in range(1, 10):
        dp[1][i][1] = 1

    # 递推计算
    for i in range(2, 10):
        for j in range(10):
            for k in range(2):
                if k == 0:
                    dp[i][j][k] = dp[i - 1][j][k] * j
                else:
                    dp[i][j][k] = dp[i - 1][j][1] * j + dp[i - 1][j - 1][0] * dp[i - 1][j][1]

    # 计算最终结果
    res = dp[len(str(n))][0][1]

    # 返回结果
    return res

# 测试代码
n = 20
result = numDupDigitsAtMostN(n)
print(result)

结语

通过这篇文章,我们不仅领略了「数位 DP」的独特魅力,还对动态规划算法有了更深入的理解。希望这篇文章对您有所启发,也希望您能继续探索算法的世界,发现更多精彩。