LeetCode中的两道动态规划题目

动态规划是一种非常重要的算法设计思想。历史上有很多著名的算法都是基于这种思想设计而来的,例如:Needleman–Wunsch算法、CYK算法、FFT算法、维特比算法等等。动态规划的核心思想有两个:首先是将一个大问题拆解为若干子问题;其次是将曾经计算过的结果储存起来以备多次使用。


动态规划非常重要,因为它是各种IT公司笔试面试题目中常常被考察的重点,而且它也是所有希望信息学竞赛的选手所必备的知识。但动态规划对于很多初学者来说,是相对较难掌握的一种技巧,LeetCode上的动态规划题目大部分属于Median和Hard程度。但通常来说,一般的动态规划问题都是有套路的,通过掌握这种套路,其实可以解决相当一部分的动态规划问题。


下面通过两个LeetCode题目来做进一步说明。首先是编号为#62的Unique Paths问题,该问题描述如下:


大意是给定一个矩阵,问你从左上角到右下角一共有多少种不同的路径。其中Robot移动的方式只能是要么向下移动一格、要么是向右移动一格。


解决动态规划问题的套路中,第一个步骤就是写出一个递归求解的“初稿”。就当前这个问题来说,要想移动到格子[m,n],要么是从[m,n-1]中(也就是左面一格)移动而来,要么是从[m-1,n]中(也就是上面一格)移动而来。于是,我们知道到达格子[m,n]的路径数目就是到达格子[m,n-1]的路径数目+到达格子[m-1,n]的路径数目,也就是最核心的递归关系(通常在动态规划问题中我们称其为状态转移方程)。然后,对于一个递归的问题,你还需要考虑到一些base condition以保证你的递归分解到原子问题时可以正确返回。根据上述思路,我们来首先写一个递归的版本(注意其中并未使用动态规划):

class Solution {
public:

    int uniquePaths(int m, int n) {
        
        if (m==1 && n==1) return 1;
        
        if(m==1) 
            return uniquePaths(m, n-1);
        
        if(n==1) 
            return uniquePaths(m-1, n);
         
        return (uniquePaths(m-1, n) + uniquePaths(m, n-1));
    }
};
但是,上面这个版本直接提交LeetCode,会显示超时!因为其中会存在大量的重复计算。为此,需要使用解决动态规划问题的套路中之第二个步骤:定义一个数组或矩阵来存储已经得到的结果。当再次需要使用它们时,直接查表,而不用重新计算。


就当前这个问题来说,不妨定义一个矩阵。因为题目中已经言明:m和n的最大值是100,而且地图的初始位置是[1,1],所以我们定义的矩阵大小为101×101,当然其中最左边一列和最上边一行并不会被用到。然后,我们把整个矩阵初始化,每个位置都是-1,表示该位置未被计算过。然后把,[1,1]这个位置初始化为1,表示如果地图只有一个格子那么路线也就只有1条。由此而得之代码如下:

class Solution {
public:
    
    int matrix[101][101];
    
    Solution(){
        for(int i = 0; i<101; i++)
            for(int j = 0; j<101; j++)
                matrix[i][j] = -1;
    
        matrix[1][1] = 1;
    }

    int uniquePaths(int m, int n) {
        
        if (matrix[m][n] != -1)
            return matrix[m][n];
        if(m==1) 
        {
            matrix[m][n] = uniquePaths(m, n-1);
            return matrix[m][n];
        }
        
        if(n==1) 
        {
            matrix[m][n] = uniquePaths(m-1, n);
            return matrix[m][n];
        }
        
        matrix[m][n] = uniquePaths(m-1, n) + uniquePaths(m, n-1);
        return (matrix[m][n]);
        
    }
};
上述实现可以满足题目要求。当然,由于为了演示之用,我们并为对其进行优化,你还可以改进它,以实现更高的效率。


LeetCode中还提供了另外一个变型题目,其编号为#63。大意是初始条件中给定一个矩阵用来表示地图,其中如果值为1则表示一个障碍物。然后再假定存在障碍物的情况下问你有多少条不同的路径。


其实如果你能解决#62题,那么这一道题目其实是非常简单的。我们主要在递归过程中,增加一个base condition,即如果目标点是一个障碍物,那么通向它的路径就为0。基于这种小改动而得之代码如下:

class Solution {
public:
    
    int matrix[101][101];
    
    Solution(){
        for(int i = 0; i<101; i++)
            for(int j = 0; j<101; j++)
                matrix[i][j] = -1; 
    }
    
    int uniquePaths(int m, int n, vector<vector<int>>& obstacleGrid) {
        
        if (matrix[m][n] != -1)
            return matrix[m][n];
        
        if(obstacleGrid[m-1][n-1] == 1)
        {
            matrix[m][n] = 0;
            return 0;
        }
        
        if(m==1) 
        {
            matrix[m][n] = uniquePaths(m, n-1, obstacleGrid);
            return matrix[m][n];
        }
        
        if(n==1) 
        {
            matrix[m][n] = uniquePaths(m-1, n, obstacleGrid);
            return matrix[m][n];
        }
        
        matrix[m][n] = uniquePaths(m-1, n, obstacleGrid) + uniquePaths(m, n-1, obstacleGrid);
        return (matrix[m][n]);
        
    }
    
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        
        matrix[1][1]= obstacleGrid[0][0]==1? 0:1;
        
        return uniquePaths(m, n, obstacleGrid);
    }
};

唯一需要注意的是作为参数而提供的新矩阵是从[0,0]开始的(而非从[1,1]开始)。所以在初始化上会有一点点的小变化。


(全文完)


本博客中已经讨论过的LeetCode题目列表


发布了358 篇原创文章 · 获赞 4283 · 访问量 419万+
展开阅读全文

旅行商问题算法流程图及时间复杂度

04-08

#include "iostream" using namespace std; int fact(int n) { //阶乘函数 int x = 1; for(int i=n;i>0;i--) x*=i; return x; } void perm(int n,FILE *fp) { int i,b,k; int *fa = new int[n+1]; //保存阶乘结果 int *r = new int[n],*r2 = new int[n]; int*num = new int[n]; //r 计算逆序数;r2计算对应位数;num保存排列结果 int tot = 0; for ( i=0;i<n+1;i++) fa[i] =fact(i); fp=fopen("data.txt","wb"); for (int count=0;count<fa[n];count++) { //一共n!个排列,对每个数,计算其对应的序列 tot = count; //r,r2 保存变进制数结果,即对应的逆序数组 for (b=n-1;b>=1;b--) { r2[n-1-b] = r[n-1-b] = tot/fa[b]; tot = tot % fa[b]; } r[n-1] = r2[n-1] = 0; //根据逆序数,计算每个数字所在位数 for ( b=1;b<n-1;b++) { for ( k=b-1;k>=0;k--) { if(r[k]<=r[b]) r2[b] ++; } } for ( i=0;i<n-1;i++) { r2[n-1] += (i+1 - r2[i]); } //根据位数计算出排列 for ( i=0;i<n;i++) { num[r2[i]] = i+1; } for(i=0;i<n;i++) fprintf(fp,"%d ",num[i]); fprintf(fp,"\n"); } fclose(fp); } void travel(int **dis,int n,int m,FILE *fp,int beginIndex) { int k=0,i; int curdis; int Mindis=10000; int **help=new int*[fact(n)]; for(i=0;i<fact(n);i++) help[i]=new int[n]; fp = fopen("data.txt","rb"); while(k<fact(n)) { curdis=0; for(i=0;i<n;i++) { fscanf(fp,"%d",&help[k][i]); } if(help[k][0]==beginIndex) { for(i=0;i<n-1;i++) { curdis += dis[help[k][i]-1][help[k][i+1]-1]; } curdis += dis[help[k][i]-1][help[k][0]-1]; if(curdis<Mindis) { Mindis=curdis; } } k++; } cout<<Mindis<<endl; fclose(fp); k=0; fp = fopen("data.txt","rb"); while(k<fact(n)) { curdis = 0; for(i=0;i<n;i++) { fscanf(fp,"%d",&help[k][i]); } if(help[k][0]==beginIndex) { for(i=0;i<n-1;i++) curdis += dis[help[k][i]-1][help[k][i+1]-1]; curdis += dis[help[k][i]-1][help[k][0]-1]; if(curdis==Mindis) { for(i=0;i<n;i++) printf("%d ",help[k][i]); printf("%d\n",beginIndex); } } k++; } fclose(fp); } int main() { int n,i,j,beginIndex; cout<<"请输入城市个数:"; cin>>n; cout<<"从第几个城市出发:"; cin>>beginIndex; int **dis=new int*[n]; for(i=0;i<n;i++) dis[i]=new int[n]; cout<<"请输入"<<n<<"阶方阵"<<endl; for(i=0;i<n;i++) for(j=0;j<n;j++) cin>>dis[i][j]; FILE *fp; perm(n,fp); travel(dis,n,n,fp,beginIndex); return 0; } 这是用穷举法解决旅行商问题的算法,跪求大神来份流程图和时间复杂度怎么算?谢大神! 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览