类型一:路径问题

225 阅读9分钟

一、动态规划

  • 本质:
  • 题目条件: 出现每次只能向下或者向右移动一步

二、思路

  • 关键词:

  • 难点在于:

    • 定义dp[i][j] 一般要求什么就是把dp[i][j]定义为啥
    • 寻找转换关系 即当前dp[i][j]的结果取自哪里?在路径问题中,一般看到其移动路径,即可确定状态转移方程。比如“只能向下或者向右移动”,则说明dp[i][j]来自dp[i-1][j]或者dp[i][j-1]
    • 确定初始值

三、题目汇总

四、题解汇总

  • Code 62

image.png

func uniquePaths(m int, n int) int {
    dp:=make([][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([]int,n)
    }
    for i:=0;i<m;i++{
        for j:=0;j<n;j++{
            if i==0{ // 向右移动
                dp[i][j]=1
            }else if j==0{ // 向下移动
                dp[i][j]=1
            }else{ // 既能向下又能向右移动
                dp[i][j]=dp[i-1][j]+dp[i][j-1]
            }
        }
    }
    return dp[m-1][n-1]
}
  • Code 63

image.png

func uniquePathsWithObstacles(obstacleGrid [][]int) int {
    m,n:=len(obstacleGrid),len(obstacleGrid[0])
    dp:=make([][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([]int,n)
    }
    for i:=0;i<m;i++{ // 向下
        if obstacleGrid[i][0]==1{
            break
        }else {
            dp[i][0]=1 
        }
    }
    for j:=0;j<n;j++{ // 向右
        if obstacleGrid[0][j]==1{
            break
        }else {
            dp[0][j]=1
        }
    }
    for i:=1;i<m;i++{
        for j:=1;j<n;j++{
            if obstacleGrid[i][j]==1{
                dp[i][j]=0
            }else{
                dp[i][j]=dp[i][j-1]+dp[i-1][j] // 向下或者向右
            }
        }
    }
    return dp[m-1][n-1]
}
  • Code 64

image.png

func minPathSum(grid [][]int) int {
    m,n:=len(grid),len(grid[0])
    dp:=make([][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([]int,n)
    }
    dp[0][0]=grid[0][0]
    for i:=0;i<m;i++{
        for j:=0;j<n;j++{
            if i>0 && j>0 {
                dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j] // 向下或者向右
            }else if i> 0{
                dp[i][j]=dp[i-1][j]+grid[i][j] // 向下
            }else if j>0 {
                dp[i][j]=dp[i][j-1]+grid[i][j] // 向右
            }
        }
    }
    return dp[m-1][n-1]
}

func min(a,b int)int{
    if a<b{
        return a
    }
    return b
}
  • Code 120

image.png

func minimumTotal(triangle [][]int) int {
    m:=len(triangle)
    dp:=make([][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([]int,m)
    }
    dp[0][0]=triangle[0][0]
    minValue:=math.MaxInt32
    for i:=1;i<m;i++{
    dp[i][0]=dp[i-1][0]+triangle[i][0]
        for j:=1;j<i;j++{
            dp[i][j]=min(dp[i-1][j],dp[i-1][j-1])+triangle[i][j]
        }
    dp[i][i]=dp[i-1][i-1]+triangle[i][i]
    }
    for j:=0;j<m;j++{
        minValue=min(minValue,dp[m-1][j])
    }
    if minValue==math.MaxInt32{
        return dp[0][0]
    }
    return minValue
}

func min(a,b int)int{
    if a<b{
        return a
    }
    return b
}

  • Code 931

image.png

func minFallingPathSum(matrix [][]int) int {
    minValue:=math.MaxInt32
    m,n:=len(matrix),len(matrix[0])
    dp:=make([][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([]int,n)
    }
    dp[0][0]=matrix[0][0]
    for j:=0;j<n;j++{
        dp[0][j]=matrix[0][j]
    }
    for i:=1;i<m;i++{
        for j:=0;j<n;j++{
            if j!= n-1 && j!=0{
                dp[i][j]=min(min(dp[i-1][j-1],dp[i-1][j]),dp[i-1][j+1])+matrix[i][j]
            }else if j==0{
                dp[i][j]=min(dp[i-1][j], dp[i-1][j+1])+matrix[i][j]
            }else{
                dp[i][j]=min(dp[i-1][j-1], dp[i-1][j])+matrix[i][j]
            }
        }
    }
    for j:=0;j<n;j++{
        minValue=min(minValue,dp[m-1][j])
    }
    return minValue
}

func min(a,b int)int{
    if a<b{
        return a
    }
    return b
}
  • Code 1289

image.png

func minFallingPathSum(grid [][]int) int {
    minValue:=math.MaxInt32
    m,n:=len(grid),len(grid[0])
    dp:=make([][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([]int, n)
    }
    for j:=0;j<m;j++{
        dp[0][j]=grid[0][j]
    }
    for i:=1;i<m;i++{ // 当前行
        for j:=0;j<n;j++{ // 当前列
            lastMinValue:=math.MaxInt32
            for k:=0;k<n;k++{ // 上一列
                if k!=j{
                    lastMinValue=min(lastMinValue,dp[i-1][k])
                }
            }
            dp[i][j]=lastMinValue+grid[i][j]
        }
    }
    for j:=0;j<n;j++{
        minValue=min(minValue, dp[m-1][j])
    }
    return minValue
}

func min(a,b int)int{
    if a<b{
        return a
    }
    return b
}
  • Code 1575
// 第一种解法:
const mod int =1000000007
func countRoutes(locations []int, start int, finish int, fuel int) int {
    dp:=make([][]int,len(locations))
    for i:=0;i<len(locations);i++{
        dp[i]=make([]int, fuel+1)
    }
    for i:=0;i<len(locations);i++{
        for j:=0;j<=fuel;j++{
            dp[i][j]=-1
        }
    }
    return dfs(locations,start,finish,fuel,dp)
}

func dfs(locations []int,cur int,finish int,fuel int, dp [][]int)int{
    if dp[cur][fuel]!=-1{
        return dp[cur][fuel]
    }
    // 第一种:油量fuel为0,但是没有达到目的地
    if fuel==0 && cur!=finish{
        dp[cur][fuel]=0
        return 0
    }
    hasNext:=false
    for i:=0;i<len(locations);i++{
        if i!=cur{
            need:=abs(locations[cur]-locations[i])
            if fuel>=need{
                hasNext=true
                break
            }
        }
    }
    // 第二种:油量fuel不为0,且无法到达下一个地址
    if fuel!=0 && !hasNext{
        if cur==finish{ // 如果恰好为目的地
            dp[cur][fuel]=1
            return 1
        } 
        // 如果不是目的地
        dp[cur][fuel]=0
        return 0
    }
    sum:=0
    if cur==finish{
        sum=1
    }
    for i:=0;i<len(locations);i++{
        if i!=cur{
            nend:=abs(locations[cur]-locations[i])
            if fuel>=nend{
                sum=sum+dfs(locations,i,finish,fuel-nend,dp)
                sum=sum%mod
            }
        }
    }
    dp[cur][fuel]=sum
    return sum
}


func abs(a int)int{
    if a>0{
        return a
    }
    return -a
}

// 第二种解法:
const mod int =1000000007
func countRoutes(locations []int, start int, finish int, fuel int) int {
    dp:=make([][]int,len(locations))
    for i:=0;i<len(locations);i++{
        dp[i]=make([]int, fuel+1)
    }
    for i:=0;i<len(locations);i++{
        for j:=0;j<=fuel;j++{
                dp[i][j]=-1  // 为了区分某个状态路径数量为0还是某个状态没有计算过
        }
    }
    return dfs(locations,start,finish,fuel,dp)
}

func dfs(locations []int,cur int,finish int,fuel int, dp [][]int)int{
    // 第一种:该状态已经计算过,则直接返回结果
    if dp[cur][fuel]!=-1{
        return dp[cur][fuel]
    }
    // 第二种:从cur到finish,一步无法直接到达,则保存结果0,并返回0;此外,一步无法到达,多步也不无法到达
    if abs(locations[cur]-locations[finish])>fuel{
        dp[cur][fuel]=0
        return 0
    }
    // 第三种:从cur到finish,查看是否可分多次到达:如果cur正好是finish,则本身算是一条路径。继续拆分多次。
    sum:=0
    if cur==finish{ 
        sum=1
    }
    for i:=0;i<len(locations);i++{
        if i!=cur{
            nend:=abs(locations[cur]-locations[i])
            if fuel>=nend{
                sum=sum+dfs(locations,i,finish,fuel-nend,dp)
                sum=sum%mod
            }
        }
    }
    dp[cur][fuel]=sum
    return sum
}


func abs(a int)int{
    if a>0{
        return a
    }
    return -a
}

// 第三种解法:
const mod int =1000000007
func countRoutes(locations []int, start int, finish int, fuel int) int {
    // dp[i][j]表示从i出发,当前油量为j时,到达目的地的路径数目
    // 状态转移方程:dp[i][fuel]=dp[i][fuel]+dp[k][fuel-need]
    // need=abs(locations[i]-locations[k])
    // i和k无明显的大小关系,但是fuel和fuel-need]有 ,因此需从小到大枚举
    dp:=make([][]int,len(locations))
    for i:=0;i<len(locations);i++{
        dp[i]=make([]int, fuel+1)
    }
    // 本身出发点就是目的地时,则路径数目为1
    for i:=0;i<=fuel;i++{
        dp[finish][i]=1
    }
    for f:=0;f<=fuel;f++{
        for i:=0;i<len(locations);i++{
            for j:=0;j<len(locations);j++{
                if i!=j {
                    need:=abs(locations[i]-locations[j])
                    if f>=need{
                        dp[i][f]=dp[i][f]+dp[j][f-need]
                        dp[i][f]=dp[i][f]%mod
                    }
                }
            }
        }
    }
    return dp[start][fuel]
}

func abs(a int)int{
    if a>0{
        return a
    }
    return -a
}
  • Code 576
// 第一种解法
const mod int = 1000000007
func findPaths(m int, n int, maxMove int, startRow int, startColumn int) int {
    dp:=make([][][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([][]int,n)
        for j:=0;j<n;j++{
            dp[i][j]=make([]int,maxMove+1)
        }
    }
    for i:=0;i<m;i++{
        for j:=0;j<n;j++{
            for k:=0;k<=maxMove;k++{
                dp[i][j][k]=-1
            }
        }
    }
    return dfs(m,n,maxMove,startRow,startColumn,dp)
}

func dfs(m int,n int,moves int,curRow int,curColum int, dp [][][]int)int{
    // 越界了,则返回1
    if curRow==-1 || curRow>=m || curColum==-1 || curColum>=n{
        return 1
    }
    // 没有步数了,则返回0
    if moves==0{
        return 0
    }
    // 如果已经走过了,则返回原数据
    if dp[curRow][curColum][moves]!=-1{
        return dp[curRow][curColum][moves]
    }
    // 如果没有走过,则继续深度累加
    sum:=0
    sum=sum+dfs(m,n,moves-1,curRow-1,curColum,dp)
    sum=sum%mod
    sum=sum+dfs(m,n,moves-1,curRow+1,curColum,dp)
    sum=sum%mod
    sum=sum+dfs(m,n,moves-1,curRow,curColum-1,dp)
    sum=sum%mod
    sum=sum+dfs(m,n,moves-1,curRow,curColum+1,dp)
    sum=sum%mod
    dp[curRow][curColum][moves]=sum
    return sum
}

// 第二种解法:
const mod int = 1000000007
func findPaths(m int, n int, maxMove int, startRow int, startColumn int) int {
    dp:=make([][][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([][]int,n)
        for j:=0;j<n;j++{
            dp[i][j]=make([]int,maxMove+1)
        }
    }
    for i:=0;i<m;i++{
        for j:=0;j<n;j++{
            for k:=0;k<=maxMove;k++{
                dp[i][j][k]=-1
            }
        }
    }
    return dfs(m,n,maxMove,startRow,startColumn,dp)
}

func dfs(m int,n int,moves int,curRow int,curColum int, dp [][][]int)int{
    // 越界了,则返回1
    if curRow==-1 || curRow>=m || curColum==-1 || curColum>=n{
        return 1
    }
    // 没有步数了,则返回0
    if moves==0{
        return 0
    }
    // 如果已经走过了,则返回原数据
    if dp[curRow][curColum][moves]!=-1{
        return dp[curRow][curColum][moves]
    }
    // 如果没有走过,则继续深度累加
    sum:=0
    dir:=[4][2]int{{1,0},{-1,0},{0,1},{0,-1}}
    for i:=0;i<len(dir);i++{
        nextRow:=curRow+dir[i][0]
        nextColum:=curColum+dir[i][1]
        sum=sum+dfs(m,n,moves-1,nextRow,nextColum,dp)
        sum=sum%mod
    }
    dp[curRow][curColum][moves]=sum
    return sum
}

// 第三种解法:
const mod int = 1000000007
func findPaths(m int, n int, maxMove int, startRow int, startColumn int) int {
    // dp[i][j][k]表示从(i,j)为起点,移动次数为k的时候,越界的路径数目
    // 状态转移方程:dp[i][j][k]=dp[i][j][k]+dp[nextRow][nextColumn][k-1]
    // i和nextRow、j和nextColumn无明显的大小关系,但是k和k-1有 ,因此需从小到大枚举
    dp:=make([][][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([][]int,n)
        for j:=0;j<n;j++{
            dp[i][j]=make([]int,maxMove+1)
        }
    }
    for k:=1;k<=maxMove;k++{
        for i:=0;i<m;i++{
            for j:=0;j<n;j++{
                // 四个边,如果是正方形的四个顶点,有两种方法越界,其他边上的位置只有一种方法越界。
                if i==0{
                    dp[i][j][k]++
                }
                if i==m-1{
                    dp[i][j][k]++
                }
                if j==0{
                    dp[i][j][k]++
                }
                if j==n-1{
                    dp[i][j][k]++
                }
                // 中间位置,来自四个方向
                dirs:=[4][2]int{{1,0},{-1,0},{0,1},{0,-1}}
                for _,dir:=range dirs{
                    nextRow:=i+dir[0]
                    nextColumn:=j+dir[1]
                    if nextRow>=0 && nextRow<m && nextColumn>=0 && nextColumn<n{
                         dp[i][j][k]=(dp[i][j][k]+dp[nextRow][nextColumn][k-1])%mod
                    }  
                }
            }
        }
    }
    return dp[startRow][startColumn][maxMove]
}
  • Code 1301
const mod int = 1000000007
func pathsWithMaxScore(board []string) []int {
    // dp[i][j]表示到达位置(i,j)的最大得分
    // path[i][j]表示到达位置(i, j) 获得最大得分的方案数
    m:=len(board)
    n:=len(board[0])
    dp:=make([][]int,m)
    path:=make([][]int,m)
    for i:=0;i<m;i++{
        dp[i]=make([]int,n)
        path[i]=make([]int,n)
    }
    dp[m-1][n-1]=0
    path[m-1][n-1]=1
    // 计算初始值
    // 计算最后一行
    for j:=n-2;j>=0 && board[m-1][j]!='X';j--{
        dp[m-1][j]=dp[m-1][j+1]+int(board[m-1][j]-'0')
        path[m-1][j]=1
    }
    // 计算最后一列
    for i:=m-2;i>=0 && board[i][n-1]!='X';i--{
        dp[i][n-1]=dp[i+1][n-1]+int(board[i][n-1]-'0')
        path[i][n-1]=1
    }
    for i:=m-2;i>=0;i--{
        for j:=n-2;j>=0;j--{
            // 存在障碍物
            if board[i][j]=='X'{
                continue
            }
            // 如果三个方向都不可达
            if path[i+1][j]==0 && path[i+1][j+1]==0 && path[i][j+1]==0{
                continue
            }
            score:=int(board[i][j]-'0')
            if board[i][j]=='E'{
                score=0
            }
            maxPreScore:=max(max(dp[i+1][j],dp[i+1][j+1]),dp[i][j+1])
            dp[i][j]=maxPreScore+score
            if maxPreScore==dp[i+1][j]{
                path[i][j]=path[i+1][j]%mod
            }
            if maxPreScore==dp[i+1][j+1]{
                path[i][j]+=path[i+1][j+1]%mod
            }
            if maxPreScore==dp[i][j+1]{
                path[i][j]+=path[i][j+1]%mod
            }
            path[i][j]=path[i][j]%mod
        }
    }
    return []int{dp[0][0],path[0][0]}
}

func max(a int,b int)int{
    if a>b{
        return a
    }
    return b
}