动态规划(DP)是计算机科学中使用的一种寻找大多数最优路径问题的乐高算法技术。而"数位动态规划"(Digit DP)是动态规划的一个特例,主要用于解决数位问题。
文章目录
1. 什么是数位问题?
数位问题,顾名思义,就是涉及到数字的各个位数的问题。例如:求一个区间内各位数字之和为N的所有数,求一个区间内,各位数字中包含1的数量,求一个区间内,各位数字中不包含13的所有数的数量等。
这类问题的直接解决方法存在时间复杂度过高的问题,这个时候就可以使用数位动态规划来解决。
2. 什么是数位动态规划?
数位动态规划是一种对数位问题进行优化求解的方法。核心技巧在于依次考虑数字的每一位数,通过动态规划递推的方式,实现最优解的寻找。
数位动态规划的具体解决手段通常是首先对给定的数以十进制的形式进行分解,然后按照一定的顺序(通常是从高位到低位)逐个处理这些位数,并根据处理过程中遇到的各种情况,进行可行性记录和状态转移,从而达到降低问题复杂度,快速求解问题的目的。
3. 数位动态规划的步骤
-
状态定义:根据问题的需求,定义各个状态所表示的含义;
-
初始状态和边境状态的选择:分析问题的初始条件,以及每个状态范围的波动情况,确定初始状态和边界状态;
-
状态转移方程的确定:根据前一个状态是如何转移到后一个状态的,确定状态转移的方程;
-
最优解的寻找:在所有的状态解中,根据问题的需求找出最优解。
4. 数位动态规划的优点
- 可以将时间复杂度较高的数位问题优化到一个相对可接受的范围内,大大节省了计算时间;
- 只需要合理设计状态和转移方程,注意边界问题,就可以直接进行问题的求解,十分适用于需要精确答案的问题。
leetcode
我们在这里通过一道LeetCode上的题目 (902. 最大为N的数字组合)来学习数位DP。
题目描述:我们有一组排序的数字 D,它是 {'1','2','3','4','5','6','7','8','9'} 的非空子集。(注意:'0' 不包括在内)。现在,我们用这些数字进行组合写数字,想用多少次都可以。例如 "111","11", "1" "123" 都是有效的组合。我们可以如同上面这样拼出的 超过 N 的部分就不需要考虑了,例如当N为 11 时,上面的 "111" 就可以忽略不计。
返回可以用以上集合中的数字拼出的小于或等于N的正整数的数目。
代码实现如下:
java
class Solution {
int[] dp = new int[64], a = new int[64];
public int atMostNGivenDigitSet(String[] D, int N) {
int m = D.length, len = 0;
for (; N > 0; a[++ len] = N % 10, N /= 10);
for (int i = 1; i < len; ++ i) dp[i] = (dp[i - 1] + 1) * m;
for (int i = len; i > 0; -- i) {
int cnt = 0;
boolean flag = false;
for (String s : D) {
int d = s.charAt(0) - '0';
if (d < a[i]) cnt ++;
else if (d == n[i]) flag = true;
}
dp[i] += cnt * Math.pow(m, i - 1);
if (!flag) break;
if (i == 1) dp[i] ++;
}
return dp[len];
}
}
这道题的解决关键是充分理解动态规划,从上一位向下一位转移的过程中思考如何利用之前的字典大小(也就是D的长度m)、是否已经与N有过大小的差异等信息,来实时递推出当前的可选方案数,从而最终得出结果。
数位DP问题的难度在于理解并确定问题的边界状态和状态转移方程。想要熟练掌握数位DP,需要不断地做题和思考,从而积累经验,深入理解其中的奥秘。
总结
总的来说,数位动态规划是一种强大的处理数位问题的工具,它结构简单、易于理解和操作,不仅可以解决复杂的数位问题,还大大提高了代码的效率。当然,效果的好坏往往取决于状态转移方程的设计,这需要良好的数学基础与实践技能。