动态规划学习

动态规划学习(一):最小路径和

提示:分享自己的浅显理解,参考了leetcode上三叶的dp教程
题目:

  1. 给定一个包含非负整数的 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]表示经过该位置需要的代价;由于只能向下或向右移动,有如下几种情况:

  1. 当前位置只能向下移动到达,dp[i][j] = dp[i-1][j] + dis[i][j],如第一列;
  2. 当前位置只能向右移动到达,dp[i][j] = dp[i][j-1] + dis[i][j],如第一行;
  3. 当前位置可以向下或向右移动到达,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]即可,同样分三种情况:

  1. 当前位置只能向上移动到达,dp[i][j] = dp[i+1][j] + dis[i][j],如第一列;
  2. 当前位置只能向左移动到达,dp[i][j] = dp[i][j+1] + dis[i][j],如第一行;
  3. 当前位置可以向上或向左移动到达,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;
}

  1. 三叶参考链接:(https://leetcode-cn.com/leetbook/read/path-problems-in-dynamic-programming/rtb68e/)

优化(待解决)

提示:优化时间复杂读O(n),空间复杂度O(n),O(1)​

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值