【学习之DP问题】动态规划(DP)之数组问题
先复习一下DP问题的两个特点:最优子结构和重复子问题。
思考:如何在o(n)的空间复杂度下完成dp的问题。
在本博文中,将总结6个题,题目如下:
1,unique paths和unique paths II
2,minimum path sum
3,Maximum Subarray 和Maximum product Subarray
4,triangle
1,unique paths和unique paths II
(1)先看一下unique paths这个题,我们假设我们已知点(i,j)之前的所有的通路,那么到达改点的道路有几条呢?从题中我们知道,从某一点只能向右或向下走一步,因此,只有(i-1,j)和(i,j-1)这两个点可以到达(i,j),故到达该点的路径也就是到达(i-1,j)和(i,j-1)两点的路径之和,用式子表示为:
dp[i][j]=dp[i][j-1] + dp[i-1][j];////dp[i][j]表示到达点(i,j)的路径条数
(2)再看一下unique paths II。这个题对(1)做了升级,在道路中设置了障碍,但是根本上还是一样的,只不过在考虑到达(i,j)的路径时,需要考虑
(i,j)是否是障碍,则式子做如下改变:
if (i,j)是障碍
dp[i][j]=0;
else
dp[i][j]=dp[i][j-1] + dp[i-1][j];
只贴一题的代码,(2)的代码如下:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
if( obstacleGrid.size()<1 ) return 0;
if( obstacleGrid[0].size()<1 ) return 0;
int m=obstacleGrid.size();
int n=obstacleGrid[0].size();
vector<vector<int>> dp;
int i,j;
for( i=0;i<m;i++ )
{
vector<int> temp_dp(n,0);
dp.push_back(temp_dp);
}//dp初始化为0的数组
if( !obstacleGrid[0][0] ) dp[0][0]=1;
for( i=0;i<m;i++ )
{
for( j=0;j<n;j++ )
{
if( j==0 && i==0 ) continue;
if( obstacleGrid[i][j] ) continue;
if( i==0 )
{
dp[i][j]=dp[i][j-1];
}
else if( j==0 )
{
dp[i][j]=dp[i-1][j];
}
else
{
dp[i][j]=dp[i][j-1] + dp[i-1][j];
}
}
}
return dp[m-1][n-1];
}
2,minimum path sum
这个题和1题算是类似的题了,同样我们考虑到达以(i,j)为终点的路径,如何从分别以(i-1,j)和(i,j-1)为终点的两条路径中选择一条路径来到达(i,j)点,根据题意,需要这条路径上和最大,因此我们要选择和最大的路径,故递推式如下:
dp_sum[i][j]=min(dp_sum[i-1][j],dp_sum[i][j-1])+grid[i][j];
其中dp_sum[i][j]表示以(i,j)为终点的路径上所有数字的和。
代码如下:
int minPathSum(vector<vector<int>>& grid) {
if( grid.size()==0 ) return 0;
int h=grid.size(),w=grid[0].size();
vector<int> temp(w,0);
vector<vector<int>> dp_sum(h,temp);
int i,j;
for( i=0;i<h;i++ )
{
for( j=0;j<w;j++ )
{
if( i==0 && j>0 )
{
dp_sum[i][j]=dp_sum[i][j-1]+grid[i][j];
}
else if( j==0 && i>0 )
{
dp_sum[i][j]=dp_sum[i-1][j]+grid[i][j];
}
else if( i>0 && j>0 )
{
dp_sum[i][j]=min(dp_sum[i-1][j],dp_sum[i][j-1])+grid[i][j];
}
else
{
dp_sum[0][0]=grid[0][0];
}
}
}
return dp_sum[h-1][w-1];
}
3,Maximum Subarray 和Maximum product Subarray
(1)该题让我们在数组nums中寻找一组连续的数seq,使该组数的和最大,为了方便得到nums中任意区间的连续数的和,首先获得数组nums的累计和的数组sums,其中sums[i]=sums[i-1]+nums[i],sums[i]的含义是数组nums中从0到i的数字的和。那么任意区间的一组数的和seq(i,j)=sums[j]-sums[i]。为使seq最大,一方面我们可以找到sums[j]的最大值,另一方面要在0到j中找到sums[i]的最小值即可。
其实该题中用到的一些的dp的思想的就是求累加和。
(2)该题和(1)是类似的,同样我们可以得到数组nums的累积pros:pros[i]=pros[i-1]*nums[i]。任意一个区间的连续数的乘机为:seq(i,j)=pros[j]/pros[i]。该题复杂在需要考虑数组中正数、0、负数三种情况下综合后的seq(i,j)的最大值,具体分析见代码:
int maxProduct(vector<int>& nums) {
vector<int> dp(nums.size()+1,1);
if( nums.size()==0 ) return 0;
if( nums.size()==1 ) return nums[0];
int i;
for( i=0;i<nums.size();i++ )
{
if( nums[i] )
dp[i+1]=dp[i]*nums[i];
}
for( i=0;i<nums.size();i++ )
{
if( nums[i]==0 )
dp[i+1]=0;
}
int max_pro=dp[1];
int min_pro=0;
for( i=0;i<nums.size();i++ )
{
if( dp[i+1] > 0)
{//对于正数情况,除数最小为1即可
int temp_max=dp[i+1];
if( temp_max>max_pro )
max_pro=temp_max;
}
else if(dp[i+1]==0)
{
max_pro=max(0,max_pro);
min_pro=0;
}
else
{//对于负数情况,除数是在0到j中最大的那个负数
if( min_pro==0 )
{
min_pro=dp[i+1];
continue;
}
int temp_max=dp[i+1]/min_pro;
if( temp_max>max_pro )
max_pro=temp_max;
if( min_pro<dp[i+1] )
min_pro=dp[i+1];
}
}
return max_pro;
}
代码中红色的3个部分,就是分别考虑了正数、0、负数的情况。
4,triangle
这个题和minimum path sum这题差不多,就是把一个矩阵换成了金字塔,题中提示了要用o(n)的空间,这样应该是大部分DP的题(不需要输出解空间的题)都可以优化的:只记录上一次的结果,因此也就不难,下一步就是找到最优子结构和重复子问题了,如下方程:
if( j==0 ) dp[j]=dp[j]+triangle[i][j];
else if( j==triangle[i].size()-1 ) dp[j]=dp[j-1]+triangle[i][j];
else dp[j]=triangle[i][j]+min(dp[j-1],dp[j]);
代码如下:
int minimumTotal(vector<vector<int>>& triangle) {
vector<int> dp(triangle.size(),0);
if( triangle.size()==0 ) return 0;
dp[0]=triangle[0][0];
for( int i=1;i<triangle.size();i++ )
{
for( int j=triangle[i].size()-1;j>=0;j-- )
{//dp[i]的状态和dp[i-1]相关,因此在循环时需要逆循环。
if( j==0 ) dp[j]=dp[j]+triangle[i][j];
else if( j==triangle[i].size()-1 ) dp[j]=dp[j-1]+triangle[i][j];
else
{
dp[j]=triangle[i][j]+min(dp[j-1],dp[j]);
}
}
}
for( int i=1;i<dp.size();i++ )
{
if( dp[i]<dp[0] ) dp[0]=dp[i];
}
return dp[0];
}