Dynamic Programming(动态规划)

Dynamic Programming(动态规划)

一、定义

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

二、方法论

动态规划的核心是状态及状态转移方程
y = d p ( x 1 , x 2 , x 3 , . . . x n ) y = dp(x1,x2,x3,...x_n) y=dp(x1,x2,x3,...xn)

d p ( x 1 , x 2 , x 3 , . . . , x n ) = k d p ( x 1 1 , x 1 2 , x 1 3 , . . x 1 n ) . . . dp(x_1,x_2,x_3,...,x_n) = kdp(x_11,x_12,x_13,..x_1n)... dp(x1,x2,x3,...,xn)=kdp(x11,x12,x13,..x1n)...

确定状态转移方程需要明确

(1)自变量

确定决定每种状态的变量有几个

例如坐标中的x,y等

(2)因变量的意义

即 dp(x1,x2,x3,…x_n)的含义

(3)状态如何变化

即当前状态与其他状态之间的关系

具体如何实施和把握,我们通过下面的题目来探索

三、题解

在这里借用leetcode上关于动态规划的几个题,谈一些自己的见解。在这里和大家一起share。

按照我自己的理解,我循序渐进得通过几个例子谈一下动态规划

Problem One

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?

这是一个简单且经典的动态规划(小学生都会做)问题。

根据上述方法论,

(1)**(自变量)**我们可以假定(i,j)代表第i行,第j列

(2)**(因变量)**dp[i][j]表示Start到(i,j)的路径数量

注意:(1)(2)的合理选取至关重要,可能影响整个算法的复杂度以及状态转移确定的难度

(3)**(状态转移)**dp[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] dp[i][j]=dp[i1][j]+dp[i][j1]

在此之前,我们还必须明确

初始化状态:

dp[0][0] = 1 dp[0][i] = 1 dp[i][0] = 1

变量细节控制

i - 1 > 0

j - 1 > 0

完成上述步骤,我们可以轻而易举写出这道题的代码

public int uniquePaths(int m, int n) {
    
    
    	//state equation
        int[][] dp = new int[m][n];
    
    	//init
        dp[0][0] = 1;
        for (int i = 1; i < n; i++) {
    
    
            dp[0][i] = dp[0][i - 1];
        }
        for (int j = 1; j < m; j++) {
    
    
            dp[j][0] = dp[j - 1][0];
        }
    
    	//transfer
        for (int i = 1; i < m; i++) {
    
    
            for (int j = 1; j < n; j++) {
    
    
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
    
        return dp[m - 1][n - 1];
}

Problem Two

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

这是Problem One的一个小变形,大家可以自行练习

只需要简单修改状态转移即可。

即对存在(i,j)位置存在障碍物时,修改dp[i][j] = 0

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    
    
    	//state equation
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];
    
    	//init
        if(obstacleGrid[0][0] == 1) {
    
          //障碍物
            return 0;
        } else {
    
    
            dp[0][0] = 1;
        }
        for (int i = 1; i < n; i++) {
    
    
            if (obstacleGrid[0][i] == 1) {
    
    	//障碍物
                dp[0][i] = 0;
            } else {
    
    
                dp[0][i] = dp[0][i - 1];
            }
        }
        for (int j = 1; j < m; j++) {
    
    
            if (obstacleGrid[j][0] == 0) {
    
    	//障碍物
                dp[j][0] = dp[j - 1][0];
            } else {
    
    
                dp[j][0] = 0;
            }
        }
    
    	//transfer
        for (int i = 1; i < m; i++) {
    
    
            for (int j = 1; j < n; j++) {
    
    
                if (obstacleGrid[i][j] == 1) {
    
    	//障碍物
                    dp[i][j] = 0;
                } else {
    
    
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
            }
        }
        return dp[m - 1][n - 1];
    }

前面的题目当然都只是小试牛刀,现在加点难度

Problem Three

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

我们依然严格执行我们的方法论

(1)自变量是什么?

不妨令(i,j)为从nums的第i个位置到第j个位置的子序列和

(2)因变量是什么?

子序列和

(3)状态怎么转移?
d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + n u m s [ j ] dp[i][j] = dp[i][j - 1] + nums[j] dp[i][j]=dp[i][j1]+nums[j]
初始化状态

dp[0][0] = nums[0];

dp[i][i] = nums[i]

看到这,so easy!!!!!

但是不妨反思一下,这种做法的复杂度是**O(n^2)**吧

这个方法虽然simple但会不会复杂度太高了呢??

显然如果你在leetcode上使用这个复杂度的代码提交,TLE就会来到你身边

因此,我们需要注意:动态规划最大限度降低复杂的有效方法是

减少自变量数目

思考过后…

发现,其实自变量一个就够了

如果利用逐步求解,以连续数组结束位置i为每一步的解,dp[i]记录了以此位置作为子序列结束位置的最大和。

此时,我们需要的子序列一定是dp中最大的一个。

事情就变得简单了。
d p [ i ] = m a x { d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] } dp[i] = max\{dp[i - 1] + nums[i], nums[i]\} dp[i]=max{ dp[i1]+nums[i],nums[i]}
因此,

public int maxSubArray(int[] nums) {
    
    
    //state euqation
    int[] dp = new int[nums.length];
    
    //init
    dp[0] = nums[0]int maxNum = dp[0];
    
   	//transfer
    for (int i = 1; i < nums.length; i++) {
    
    
        dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
        if (dp[i] > maxNum) {
    
    
            maxNum = dp[i];
        }
    }
    
    return maxNum;
}

未完待续…稍后更新新的题目

猜你喜欢

转载自blog.csdn.net/qq_21515253/article/details/95683243