vlambda博客
学习文章列表

【学习之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


【学习之DP问题】动态规划(DP)之数组问题


(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

【学习之DP问题】动态规划(DP)之数组问题

这个题和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


【学习之DP问题】动态规划(DP)之数组问题

【学习之DP问题】动态规划(DP)之数组问题


(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];
}