「程序员必须掌握的算法」动态规划「中篇」

简介: 「程序员必须掌握的算法」动态规划「中篇」

「程序员必须掌握的算法」动态规划「中篇」

在程序员的日常工作中,掌握各种算法是必不可少的。其中动态规划是常用的一种算法,在解决优化问题方面有着广泛的应用。本文主要介绍动态规划的中等难度内容,包括二维DP和数位DP。

二维DP

二维动态规划(DP)是指,用一个二维数组来表示状态,其中第一维表示选取到哪个元素,第二维表示当前选取的状态。这种方法在解决问题中,需要转移的状态比较复杂的情况下特别有用。

例题1:矩阵路径计数

题目描述:给定一个 n × m n \times mn×m 的矩阵,从左上角走到右下角,一共有多少种走法。其中每次只能向右或向下走。

思路分析:定义状态 d p i , j dp_{i,j}dpi,j 表示从 ( 1 , 1 ) (1,1)(1,1) 走到 ( i , j ) (i,j)(i,j) 的方案数,那么状态转移方程就可以定义为:d p i , j = d p i − 1 , j + d p i , j − 1 dp_{i,j} = dp_{i-1,j} + dp_{i,j-1}dpi,j=dpi1,j+dpi,j1,其中 d p i − 1 , j dp_{i-1,j}dpi1,j 表示从上面的位置走到 ( i , j ) (i,j)(i,j)d p i , j − 1 dp_{i,j-1}dpi,j1 表示从左边的位置走到 ( i , j ) (i,j)(i,j),两种情况的方案数相加即可得到从 ( 1 , 1 ) (1,1)(1,1) 走到 ( i , j ) (i,j)(i,j) 的方案数。特别地,当 i = 1 i=1i=1j = 1 j=1j=1 时,只有一条路径,因此 d p 1 , j = d p i , 1 = 1 dp_{1,j}=dp_{i,1}=1dp1,j=dpi,1=1

最后,返回 d p n , m dp_{n,m}dpn,m 就是从左上角走到右下角的方案数。

int[][] dp = new int[n][m];
for (int i = 0; i < n; i++) {
    dp[i][0] = 1;
}
for (int i = 0; i < m; i++) {
    dp[0][i] = 1;
}
for (int i = 1; i < n; i++) {
    for (int j = 1; j < m; j++) {
        dp[i][j] = dp[i-1][j] + dp[i][j-1];
    }
}
return dp[n-1][m-1];

数位DP

数位DP是指,用一个数字的各个位数作为状态,来解决数字相关问题的一种算法。数位DP的思路是把数字拆开,按照位数逐位处理,求解每一位的方案数,最后组合得到最终的结果。

例题2:数字和为 k kk 的数的个数

题目描述:给定两个正整数 a aab bb,求满足 a ≤ x ≤ b a \leq x \leq baxb,并且 x xx 的所有数字之和等于 k kk 的自然数的个数。

思路分析:我们可以用数位DP的方法来解决这个问题。和前面的例题一样,我们定义状态 d p i , j , k dp_{i,j,k}dpi,j,k 表示当前处理到第 i ii 位,数字和为 j jj 的方案数。那么状态转移方程可以定义为:d p i , j , k = ∑ d = 0 9 d p i − 1 , j − d , k − 1 dp_{i,j,k} = \sum \limits _{d=0} ^9 dp_{i-1,j-d,k-1}dpi,j,k=d=09dpi1,jd,k1,其中 d dd 表示当前处理到的数字,也就是第 i ii 位的数字。那么 d p i − 1 , j − d , k − d dp_{i-1,j-d,k-d}dpi1,jd,kd 就表示前 i − 1 i-1i1 位数字和为 j − d j-djd,数字个数为 k − 1 k-1k1 的方案数,然后把所有 d dd 的情况都累加到一起即可得到 d p i , j , k dp_{i,j,k}dpi,j,k 的值。特别地,当 i = 0 i=0i=0j = k = 0 j=k=0j=k=0 时,可以将此视为一个合法方案,即 d p 0 , 0 , 0 = 1 dp_{0,0,0}=1dp0,0,0=1

下面,我们对数位DP的状态进行优化,因为对于一个数字 n nn,它的各个数位的数字之和最大为 9 99,因此 d p i , j , k = 0 dp_{i,j,k}=0dpi,j,k=0j > 9 k j>9kj>9k,即当前处理到第 i ii 位,数字和大于 9 k 9k9k 时可以直接跳过。另外,我们为了方便计算,可以将上限转化成字符串,比如将 b = 1234 b=1234b=1234 转化为 s = " 1234 " s="1234"s="1234",然后逐位考虑,如果 s i < d s_i<dsi<d,那么当前数字不能取 d dd,下一位需要继续考虑,如果 s i = d s_i=dsi=d,那么当前数字可以取 d dd,下一位也需要继续考虑,如果 s i > d s_i>dsi>d,那么下一位可以任意填充数字,无需继续考虑。

在实现的时候,我们需要用一个 s u m sumsum 变量来记录当前数字的各个位数之和,把所有状态的方案数相加,得到最终的结果。

public int count(int n, int k) {
    int[][][] dp = new int[11][91][91];
    dp[0][0][0] = 1;
    for (int i = 1; i <= 10; i++) {
        for (int j = 0; j <= 81; j++) {
            for (int l = 0; l <= 81; l++) {
                for (int d = 0; d <= 9; d++) {
                    if (j >= d && l >= d) {
                        dp[i][j][l] += dp[i - 1][j - d][l - d];
                    }
                }
            }
        }
    }
    int ans = 0;
    String s = String.valueOf(n);
    int[] num = new int[s.length() + 1];
    for (int i = 0; i < s.length(); i++) {
        num[i + 1] = s.charAt(i) - '0';
    }
    for (int i = 1; i < num.length; i++) {
        for (int j = 0; j < num[i]; j++) {
            if (j <= k - ans - (num.length - i) * 9) {
                ans += dp[num.length - i][k - ans - j][(num.length - i) * 9 + j];
            }
        }
        if (num[i] > k - ans - (num.length - i) * 9) {
            break;
        }
        sum += num[i];
    }
    if (sum == k) {
        ans++;
    }
    return ans;
}

结语

本文主要介绍了动态规划的中等难度内容,包括二维DP和数位DP,并附有两道例题作为模版参考。通过实际运用和练习,相信读者可以更加深入地理解动态规划的思想和应用。

相关文章
|
1月前
|
存储 算法
深入了解动态规划算法
深入了解动态规划算法
57 1
|
1月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
10天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
28 2
|
1月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
65 2
动态规划算法学习三:0-1背包问题
|
1月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
65 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
1月前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
119 0
动态规划算法学习二:最长公共子序列
|
1月前
|
负载均衡 监控 算法
每个程序员都应该知道的 6 种负载均衡算法
每个程序员都应该知道的 6 种负载均衡算法
97 2
|
1月前
|
存储 人工智能 算法
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
|
1月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
101 0
|
1月前
|
算法 C++
【算法解题思想】动态规划+深度优先搜索(C/C++)
【算法解题思想】动态规划+深度优先搜索(C/C++)