返回
挖掘「数位 DP」的潜力:动态规划算法的巧妙运用
后端
2024-01-20 13:26:49
当然可以,以下是动态规划算法解析:
动态规划算法的魅力
动态规划是一种解决复杂问题的经典算法范式。其核心思想在于将问题分解成一系列子问题,并通过子问题的最优解来递推得到原问题的最优解。这种「从下而上」的思路使得动态规划算法在解决许多现实问题时表现出优异的性能。
「数位 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」的独特魅力,还对动态规划算法有了更深入的理解。希望这篇文章对您有所启发,也希望您能继续探索算法的世界,发现更多精彩。