动态规划学习(一):最小路径和
提示:分享自己的浅显理解,参考了leetcode上三叶的dp教程
题目:
- 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
示例2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12
提示:
:m == grid.length
:n == grid[i].length
:1 <= m, n <= 200
:0 <= grid[i][j] <= 100
分析
提示:动态规划的一个重要的地方就是状态定义和状态转移方程
思路一:
用一个二维数组dp保存最小路径和,dp[i][j]表示到位置(i,j)时的最小路径,dis[i][j]表示经过该位置需要的代价;由于只能向下或向右移动,有如下几种情况:
- 当前位置只能向下移动到达,dp[i][j] = dp[i-1][j] + dis[i][j],如第一列;
- 当前位置只能向右移动到达,dp[i][j] = dp[i][j-1] + dis[i][j],如第一行;
- 当前位置可以向下或向右移动到达,dp[i][j] = (dp[i-1][j] + dis[i][j],dp[i][j-1] + dis[i][j])的小者。
最后,返回dp[m-1][n-1]
代码如下:
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(),n = grid[0].size(); //获取数组的行,列
vector<vector<int>> dp(m,vector<int>(n,0)); //定义一个二维数组,codeblocks中 申请二维数组<>中间要留个空格,vector< vector<int> >
int i,j;
dp[0][0] = grid[0][0];
for(i=1;i<m;i++)
dp[i][0] = dp[i-1][0]+grid[i][0]; //情况一
for(j=1;j<n;j++)
dp[0][j] = dp[0][j-1]+grid[0][j]; //情况二
for(i=1;i<m;i++) //其余情况
{
for(j=1;j<n;j++)
{
dp[i][j] = min(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j]);
}
}
return dp[m-1][n-1];
}
};
思路二:
同样用一个二维数组dp保存最小路径和,dp[i][j]表示到位置(i,j)时的最小路径,dis[i][j]表示经过该位置需要的代价;前面是从起点到终点计算,此题也可以看作从终点到起点的最小路径和,然后输出dp[0][0]即可,同样分三种情况:
- 当前位置只能向上移动到达,dp[i][j] = dp[i+1][j] + dis[i][j],如第一列;
- 当前位置只能向左移动到达,dp[i][j] = dp[i][j+1] + dis[i][j],如第一行;
- 当前位置可以向上或向左移动到达,dp[i][j] = (dp[i+1][j] + dis[i][j],dp[i][j+1] + dis[i][j])的小者。
代码如下:
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(),n = grid[0].size();
vector<vector<int>> dp(m,vector<int>(n,0));
int i,j;
dp[m-1][n-1] = grid[m-1][n-1];
for(i=m-2;i>=0;i--)
dp[i][n-1] = dp[i+1][n-1]+grid[i][n-1];
for(j=n-2;j>=0;j--)
dp[m-1][j] = dp[m-1][j+1]+grid[m-1][j];
for(i=m-2;i>=0;i--)
{
for(j=n-2;j>=0;j--)
{
dp[i][j] = min(dp[i+1][j]+grid[i][j],dp[i][j+1]+grid[i][j]);
}
}
return dp[0][0];
}
};
拓展(输出路径最短的路径)
思路一:用一个path数组保存路径上的位置,然后回溯dfs输出,也可以前序遍历输出(结合上面的思路二)
代码如下:
/*逆向求dp数组,再用前序遍历输出*/
#include<iostream>
#include<algorithm>
#include<vector>
#include<stdio.h>
int m,n;
using namespace std;
void printPath(vector< vector<int> > &path,int i,int j) //相当于前序遍历二叉树
{
cout <<"("<<i<<","<<j<<")"<<endl;
if(i==m-1 && j==n-1)
{
//cout <<"("<<i<<","<<j<<")"<<endl;
return ;
}
if(path[i][j]==0)
printPath(path,i+1,j);
else
printPath(path,i,j+1);
//cout <<"("<<i<<","<<j<<")"<<endl;
}
int main()
{
cin >>m>>n;
vector< vector<int> > grid(m,vector<int>(n,0));
vector< vector<int> > dp(m,vector<int>(n,0));
vector< vector<int> > path(m,vector<int>(n,0)); //路径数组,0表示从下面到达,1表示从右边到达
int i,j;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
cin>>grid[i][j];
dp[m-1][n-1] = grid[m-1][n-1];
for(i=m-2;i>=0;i--)
{
dp[i][n-1] = dp[i+1][n-1]+grid[i][n-1];
path[i][j] = 0;
}
for(j=n-2;j>=0;j--)
{
dp[m-1][j] = dp[m-1][j+1]+grid[m-1][j];
path[i][j]=1;
}
for(i=m-2;i>=0;i--)
{
for(j=n-2;j>=0;j--)
{
int bottom = dp[i+1][j]+grid[i][j]; //从下面位置到达
int right = dp[i][j+1]+grid[i][j];
if(bottom<right)
{
dp[i][j] = bottom;
path[i][j] = 0;
}
else
{
dp[i][j] = right;
path[i][j] = 1;
}
//dp[i][j] = min(dp[i+1][j]+grid[i][j],dp[i][j+1]+grid[i][j]);
}
}
cout <<"打印路径:"<<endl;
printPath(path,0,0);
cout <<"最短路径为:"<<dp[0][0]<<endl;
return 0;
}
代码运行截图:
二:回溯输出
代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<stdio.h>
int m,n;
using namespace std;
//
void printPath(vector< vector<int> > &path,int i,int j)
{
//cout <<"("<<i<<","<<j<<")"<<endl;
if(i==0 && j==0)
{
cout <<"("<<i<<","<<j<<")"<<endl;
return ;
}
if(path[i][j]==0)
printPath(path,i-1,j);
else
printPath(path,i,j-1);
cout <<"("<<i<<","<<j<<")"<<endl; //回溯
}
int main()
{
cin >>m>>n;
vector< vector<int> > grid(m,vector<int>(n,0));
vector< vector<int> > dp(m,vector<int>(n,0));
vector< vector<int> > path(m,vector<int>(n,0));
int i,j;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
cin>>grid[i][j];
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
if(i==0 && j==0)
dp[i][j] = grid[i][j];
else
{ //这里没有采用前面的分情况处理path数组,分情况求dp时path路径数组不方便,用个三目运算符简化
int top = i-1>=0 ? dp[i-1][j]+grid[i][j]:INT_MAX;
int right = j-1>=0 ? dp[i][j-1]+grid[i][j]:INT_MAX;
if(top<right)
{
dp[i][j] = top;
path[i][j] = 0;
}
else
{
dp[i][j] = right;
path[i][j] = 1;
}
}
}
}
cout <<"打印路径:"<<endl;
printPath(path,m-1,n-1);
cout <<"最短路径为:"<<dp[m-1][n-1]<<endl;
return 0;
}
思路三(参考三叶的代码,不需递归):
代码如下:
在这里#include<iostream>
#include<algorithm>
#include<vector>
#include<stdio.h>
int m,n;
int getId(int x,int y)
{
return n*x+y;
}
int row(int idx) //得到横坐标
{
return idx/n;
}
int col(int idy) //得到纵坐标
{
return idy%n;
}
using namespace std;
int main()
{
cin >>m>>n;
vector< vector<int> > grid(m,vector<int>(n,0));
vector< vector<int> > dp(m,vector<int>(n,0));
vector<int> vis(m*n,0);
int i,j;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
cin>>grid[i][j];
for(i=m-1;i>=0;i--)
{
for(j=n-1;j>=0;j--)
{
if(i==m-1 && j==n-1)
dp[i][j] = grid[i][j];
else
{
int b = i+1<m ? dp[i+1][j]+grid[i][j]:INT_MAX;
int r = j+1<n ? dp[i][j+1]+grid[i][j]:INT_MAX;
dp[i][j] = min(b,r);
vis[getId(i,j)] = b<r ? getId(i+1,j):getId(i,j+1);
}
}
}
int idx = getId(0,0);
cout <<"最短路径为:"<<endl;
for(i=0;i<m+n-1;i++)
{
cout <<"("<<row(idx)<<","<<col(idx)<<")"<<endl;
idx = vis[idx];
}
//cout <<"("<<row(idx)<<","<<col(idx)<<")"<<endl;
cout <<"最短路径为:"<<endl;
cout <<dp[0][0];
return 0;
}
- 三叶参考链接:(https://leetcode-cn.com/leetbook/read/path-problems-in-dynamic-programming/rtb68e/)
优化(待解决)
提示:优化时间复杂读O(n),空间复杂度O(n),O(1)