一、动态规划
- 本质:
- 题目条件: 出现每次只能向下或者向右移动一步 。
二、思路
-
关键词:
-
难点在于:
- 定义dp[i][j] 一般要求什么就是把dp[i][j]定义为啥
- 寻找转换关系 即当前dp[i][j]的结果取自哪里?在路径问题中,一般看到其移动路径,即可确定状态转移方程。比如“只能向下或者向右移动”,则说明dp[i][j]来自dp[i-1][j]或者dp[i][j-1]
- 确定初始值
三、题目汇总
- 62. 不同路径
- 63. 不同路径 II
- 64. 最小路径和
- 120. 三角形最小路径和
- 931. 下降路径最小和
- 1575. 统计所有可行路径
- 576. 出界的路径数
- 1301. 最大得分的路径数目
四、题解汇总
- Code 62
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
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
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
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
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
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
}