动态规划算法学习总结(带案例)

178 阅读2分钟

【动规算法学习总结】


首先,遇到动态规划问题要找到三个重要元素: 1.最优子结构 2.边界 3.状态转移方程

【最优子结构】 通俗来说,就是具有规律性的结果的获取方式。 如上楼梯问题中, 上第10层的情况种类 = 上第8、9层的情况种类之和。第9层的结果又为第7、8层结果之和。 又如击鼓传花问题中。 传m次传给1的情况种类 = 传m-1次传给n、2的情况种类之和。 传m-1次传给2的情况又为传m-2次传给1、3情况之和。 (详细内容见另外两篇博客)

【边界】 任何问题都要有边界,否则我们就要无休止的循环下去了。 在得出最优子结构后,在将结果递归至某一处时(一般在数据集边缘),可以直接得出其结果。而这个就是边界。 如上楼梯问题(初始位置为第0阶楼梯),其边界为:上第1阶时,结果为1。 上第二阶时,结果为2({1,1},{2}})。 又如击鼓传花问题,传1次传给2的结果为1,传1次传给n的结果为1。

【状态转移方程】 在得出最优子结构后,就可以对应归纳出状态转移方程。 如上楼梯问题,F(n) = F(n-1) + F(n-2) 但是我个人觉得,如果时间紧迫(如笔试题),也可以放弃得到其最优子结构和最优的状态转移方程。 而是采用判断的形式,得出对应的结果。 如在击鼓传花问题当中,我将其分为三种情况:

 * dp[m][1]等于第m-1次传递到小赛左右两边的人的情况之和,即 dp[m][1] = dp[m-1][n] + dp[m-1][2]
 * i代表传递i次,j代表传递给第j个人,则
 * j == 1时:
 *   dp[i][j] = dp[i-1][n] + dp[i-1][j+1];
 * j == n时:
 *   dp[i][j] = dp[i-1][j-1] + dp[i-1][1];
 * 正常情况:
 *   dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1];

而对应的代码,同样是判断

//dynamic planing
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if(j == 1){
                    dp[i][j] = dp[i-1][n] + dp[i-1][j+1];
                }else if(j == n){
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][1];
                }else{
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1];
                }
            }
        }
        System.out.println(dp[m][1]);

其次,在得到三个元素后,需要将思路逆转过来 为什么说逆转过来呢? 因为刚才我们得到的最优子结构,是采用自顶向下的思路推理分析的。而我们所采用的动态规划解法,则自底而上,从我们找到的边界值开始,从底层,采用for循环,一步一步得到上方的值,最终得到我们最顶端的值。 举例说明:

【上楼梯问题】 知道1阶和2阶的结果,我们就可以得到3阶的结果。 知道2阶和3阶的结果,我们就可以得到4阶的结果。 ....... 最终,我们得到了10阶的结果。 具体代码如下:

static int step(int n){
        if (n == 1){return 0; }
        if (n == 2){return 1; }
        if (n == 3){return 2; }
        int a = 1, b = 2, step = 0;
        for (int i = 4; i <= n; i++){
            step = a + b;
            a = b;
            b = step;
        }
        return step;
    }

【击鼓传花问题】 已知dp[1][2]=1,dp[1][n]=1,其余dp[1][i]=0。 可以根据dp[1][2]=1,dp[1][n] = 1 ,得到dp[2][1] = 2; 根据dp[1][1]=0,dp[1][3] = 0,得到dp[2][2] = 0; 根据dp[1][2] = 1, dp[1][4] = 0,得到 dp[2][3] = 1; ...... 最终得到dp[m][1]的结果。

以上就是自己学习动态规划的总结。


【补充内容】 其实,在得到【最优子结构】,【边界】,【状态转移方程】后最直接的解法是: 采用递归算法(时间复杂度高,空间复杂度高) 以及备忘录算法(时间复杂度低,空间复杂度较高) 最优的才是动态规划算法

简要介绍一下这里提到的两种算法: 【递归算法】 以上楼梯问题为例:

int step(int n){
	if(n == 0)
		return 0;
	if(n == 1)
	    return 1;
	if(n == 2)
	    return 2;
	return step(n-1) + step(n-2);
}

递归最为简单,直接把边界值,状态转移方程带入求解即可。 但是效率极低,因为其它重复计算了很多内容。(一旦n值极大,程序马上崩溃)。

【备忘录算法】 思路:采用递归算法,但是增加一个缓冲池,每次取值时, 判断缓冲池中是否有?取出:计算得出并放入缓冲池 伪代码如下:

Map<Integer,Integer> cache = new HashMap<>();
int step(int n){
	if(n == 0)
		return 0;
	if(n == 1)
	    return 1;
	if(n == 2)
	    return 2;
	if(cache.contains(n){
		return map.get(n)
	}else{
		int res = step(n-1) + step(n-2);
		cache.put(n,res);
		return res;
	}
}