动态规划

196 阅读3分钟

动态规划问题思考分为4步: 定义状态,思考状态转移方程,思考初始值,思考输出

第一步:定义状态

不同的状态定义会使得转移方程的复杂度不同。

最大子序和中,我自己定义的dp[i]的含义是从下标0开始长度为i的数组的最大子序列,这个定义导致转移方程非常复杂。题解的定义为dp[i]表示以nums[i]结尾的连续子数组的最大和。这样定义的状态方程十分简单。

怎么想到这么定义的呢?首先一个连续子序列一定要以一个数作为结尾,再者凭经验,和类似的题目最长上升子序列按摩师

第二步:思考状态转移方程

状态转移指的是从「状态i-1」转移到「状态i」。

发生转移的原因是你做了某种操作,弄清楚在当前状态下你可以做哪些操作对写出状态转移方程很关键。 如买卖股票的最佳时机中,dp[i]表示截止到第i天的最大收益,从状态i-1到状态i,你可以对股票进行两种操作:

  • 无操作(或者理解成当天买入又卖出)
  • 卖出

没有买入操作是因为,截止到第i天的最大收益一定不会持有股票。当进行第一种操作时,截止到第i天的最大收益等于截止到第i-1天的最大收益,即dp[i]=dp[i-1]。

当进行第二种操作时,最大收益取决于哪一天买入,因此dp[i]=第i天的价格-前i-1天中最小价格。

因此,转移方程为 前i天的最大收益=max{前i-1天的最大收益,第i天的价格-前i-1天中最小价格}。

例题

不同路径

不同路径

求解dp时,要注意求解顺序,如此题中状态转移方程是dp[i][j]=dp[i+1][j]+dp[i][j+1];状态(i,j)取决于状态(i+1.j)和(i,j+1),因此循环时i和j都应该递减

class Solution {
    public int uniquePaths(int m, int n) {
    	if (m==1 || n==1) {
    		return 1;
    	}
    	int[][] dp=new int[m][n];
    	dp[m-1][n-1]=1;
    	//dp[i][j]=dp[i+1][j]+dp[i][j+1];
    	for (int i=m-1;i>=0;i--) {
    		for (int j=n-1;j>=0;j--) {
    			if (i==m-1 || j==n-1) {
    				dp[i][j]=1;
    				continue;
    			}
    			dp[i][j]=dp[i+1][j]+dp[i][j+1];
    		}
    	}
    	return dp[0][0];
    }
   
}

不同路径Ⅱ

不同路径Ⅱ

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    	int m=obstacleGrid.length;
    	int n=obstacleGrid[0].length;
    	int rowOb=-1,colOb=-1;//最后一行和最右边一列的障碍位置
    	int[][] dp=new int[m][n];
    	for (int j=n-1;j>=0;j--) {
    		if (obstacleGrid[m-1][j]==1) {
    			rowOb=j;
    			break;
    		}
    	}
    	for (int i=m-1;i>=0;i--) {
    		if (obstacleGrid[i][n-1]==1) {
    			colOb=i;
    			break;
    		}
    	}
    	for (int i=m-1;i>=0;i--) {
    		for (int j=n-1;j>=0;j--) {
    			if (obstacleGrid[i][j]==1) {
    				continue;
    			}
    			if (i==m-1) {
    				if (j>rowOb) {
    					dp[i][j]=1;
    				}else {
    					dp[i][j]=0;
    				}
    				continue;
    			}
    			if (j==n-1) {
    				if (i>colOb) {
    					dp[i][j]=1;
    				}else {
    					dp[i][j]=0;
    				}
    				continue;
    			}
    			dp[i][j]=dp[i+1][j]+dp[i][j+1];
    		}
    	}
    	return dp[0][0];

    }
}

最长公共子序列

最长公共子序列

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
    	int len1=text1.length();
    	int len2=text2.length();
    	int[][] dp=new int[len1][len2];
    	if (text1.charAt(0)==text2.charAt(0))
    		dp[0][0]=1;
    	for (int i=1;i<len1;i++) {
    		if (text1.charAt(i)!=text2.charAt(0))
    			dp[i][0]=dp[i-1][0];
    		else
    			dp[i][0]=1;
    	}
    	for (int j=1;j<len2;j++) {
    		if (text1.charAt(0)!=text2.charAt(j))
    			dp[0][j]=dp[0][j-1];
    		else
    			dp[0][j]=1;
    	}
    	for (int i=0;i<len1;i++) {
    		for (int j=0;j<len2;j++) {
    			if (i==0 || j==0)
    				continue;
    			if (text1.charAt(i)==text2.charAt(j))
    				dp[i][j]=dp[i-1][j-1]+1;
    			else 
    				dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
    		}
    	}
    	return dp[len1-1][len2-1];
    }
    
}

自由之路

自由之路