题目叙述
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
分析
本题属于选择决策问题,因此可以使用多选择深度搜索,同时也可以使用动态求解
方法1:深度搜索(超时)
使用深度搜索来求解问题一般有两种思路:
递归的两种方案:
自顶向下” 的解决方案:首先得到最顶层的值,每递归一层计算中间结果,最终结果在递归到最下面一层函数运行完得到(比如全排列问题或者n皇后问题);
自底向上” 的解决方案:首先递归到最下面一层得到基准值,然后从最下面一层函数开始计算中间结果并逐步退出递归,最终结果在最顶层函数计算完得到。动态规划也常使用自底向上
本题采用自顶向下(这里的下方也就是基准值容易求的点)的思想。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int res;
int m = grid.size();
int n = grid[0].size();
return dfs(m-1, n-1, grid);
}
private:
int dfs(int i, int j, vector<vector<int>>& grid){
if(i==0&&j==0){
return grid[0][0];
}
if (i < 0 || j < 0)
return INT_MAX;
return grid[i][j] + min(dfs(i -1, j, grid),dfs(i,j-1,grid));
}
};
上面的解法是超时的,一般像这种尾递归都可以使用记忆化递归。
class Solution {
public:
map<pair<int, int>, int> dic;
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
return dfs(m-1, n-1, grid);
}
private:
int dfs(int i, int j, vector<vector<int>>& grid){//注意此处一定要写成& grid直接写成grid仍会超时,否则应该存在一个值拷贝的耗时过程
pair<int, int> p = make_pair(i, j);
if(dic[p]) return dic[p];
if(i==0&&j==0){
dic[p] = grid[0][0];//这句话也可以不写
return grid[0][0];
}
if (i < 0 || j < 0)
return INT_MAX;
dic[p] = grid[i][j] + min(dfs(i -1, j, grid),dfs(i,j-1,grid));
return dic[p];
}
};
或者写成如下的写法:
class Solution {
public:
//定义一个备忘录,备忘录通常是以map或者是数组充当
vector<vector<int>> dp;
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
dp.resize(m, vector<int>(n, -1));//-1表示备忘录还没赋值
return dfs(grid, m-1, n-1);
}
//定义深度搜索数组,采用自顶向下
int dfs(vector<vector<int>>& grid, int x, int y){//注意此处一定要写成& grid直接写成grid仍会超时,否则应该存在一个值拷贝的耗时过程
if(x<0 || y<0) return INT_MAX;
if(x==0 && y==0){
dp[x][y] = grid[0][0];
return grid[0][0];
}
if(dp[x][y]>=0) return dp[x][y];
//备忘录第一步,先给相应的位置进行赋值
dp[x][y] = min(dfs(grid, x-1, y), dfs(grid, x, y-1)) + grid[x][y];
//备忘录第二步,返回当前位置的备忘录记录
return dp[x][y];
}
};
方法2:动态规划
使用二维dp
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
if (grid.size() == 0 || grid[0].size() == 0) {
return 0;
}
int rows = grid.size(), columns = grid[0].size();
auto dp = vector < vector <int> > (rows, vector <int> (columns));
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
};
由于根据状态转移方程可知,dp[i][j] 只与当前行和上一个行的一个位置有关,而与更上面的行没有关系,那么就可以使用滚动数组进行优化空间复杂度。开辟一个长度为columns长度的一维数组作为dp数组。我们以当前的当前的还未更新的dp[i]表示 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j] , 以dp[i-1]表示 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1]。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int rows = grid.size();
int columns = grid[0].size();
vector<int>dp (columns, INT_MAX);
dp[0] = 0;
for (int i = 0; i < rows; i++) {
dp[0] += grid[i][0];//仍旧是一行一行的处理但是对于每次处理新行,第一个位置也就是第一列都是要加上当前的行的第一个元素
for (int j = 1; j < columns; j++) {
dp[j] = min(dp[j], dp[j-1]) + grid[i][j];
}
}
return dp[columns - 1];
}
};
思考:求出其中的一条路径
这个求路径的思想也是基于方法1的递归思想以及记忆化的备忘录倒着求解。
class Solution {
public:
map<pair<int, int>, int> dic;
int minPathSum(vector<vector<int>>& grid) {
int res;
int m = grid.size();
int n = grid[0].size();
int ans = dfs(m-1, n-1, grid);
vector<int> res_path;
path(grid, dic, res_path, grid.size()-1, grid[0].size()-1);
//由于这个路径是逆着求解的,所以要逆置一下
reverse(res_path.begin(), res_path.end());
for(auto it:res_path) cout<<it<<" ";
return ans;
}
private:
int dfs(int i, int j, vector<vector<int>>& grid){
pair<int, int> p = make_pair(i, j);
if(dic[p]) return dic[p];
if(i==0&&j==0){
dic[p] = grid[0][0];
return grid[0][0];
}
if (i < 0 || j < 0)
return INT_MAX;
dic[p] = grid[i][j] + min(dfs(i -1, j, grid),dfs(i,j-1,grid));
return dic[p];
}
//通过一个递归进行求路径
//倒着寻找路径
void path(vector<vector<int>>& grid, map<pair<int, int>, int>& dic, vector<int>& res_path, int i, int j){
if(i<0 || j<0) return;
if(i==0 && j==0){
res_path.push_back(grid[0][0]);
return;
}
res_path.push_back(grid[i][j]);
if((i-1)<0){
path(grid, dic, res_path, i, j-1);
}else if((j-1)<0){
pair<int, int> p1 = make_pair(i-1, j);
}
else{
pair<int, int> p1 = make_pair(i-1, j);
pair<int, int> p2 = make_pair(i, j-1);
if(dic[p1]<dic[p2]) path(grid, dic, res_path, i-1, j);
else path(grid, dic, res_path, i, j-1);
}
}
};