动态规划解析:GO算法题解-动态规划-原理&&优化_aichojie的博客-CSDN博客
一:爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
解题思路:
每次走1,2想到斐波那契数,其次这是一个重复的操作,所以可以走动态规划,递归
func climbStairs(n int) int {
//处理极端情况
if n < 3 {
return n
}
//一次走1个台阶
var one = 1
//一次走2个台阶
var two = 2
sum := 0
for i := 3; i <= n; i ++ {
sum = one + two
//从底向上,每次把最近两次所有的可能都存起来,然后就只要考虑下一层有几种走法,直接想加,动态的用最新的层数替换调老的层数
one = two
two = sum
}
return sum
}
二:不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
解题思路:首先这是一个动态的问题,每一步都有两种走法,下一个目标,又依赖于上和左,所以假定目标m,n 则f(m,n) = f(m,(j-1)) + f((m-1),j)
func uniquePaths(m int, n int) int {
//思考:首先这是一个动态的问题,每一步都有两种走法,下一个目标,又依赖于上和左
//所以假定目标m,n 则f(m,n) = f(m,(j-1)) + f((m-1),j)
//暴力递归解法
//构建递归底部的值与终止情况
//都是1不用移动
if m == 1 && n == 1 {
return 0
//列出直走1步的情况
} else if (m == 1 && n == 2) || ( m == 2 && n == 1) {
return 1
//列出完结情况
} else if m < 0 || n < 0 {
return 0
}
//构造递归算法
return uniquePaths(m, n - 1) + uniquePaths(m - 1, n)
}
上面方法可以进行 递归 + 记忆 + 降维 或者 动态规划来进行优化
//递归+记忆
func uniquePaths(m int, n int) int {
//思考:首先这是一个动态的问题,每一步都有两种走法,下一个目标,又依赖于上和左
//所以假定目标m,n 则f(m,n) = f(m,(j-1)) + f((m-1),j)
//通过map 来记录每一个目标需要的步数
var memory map[string]int
memory = make(map[string]int, m*n)
return parton(m, n, memory)
}
func parton(m int, n int, memory map[string]int) int {
//先看是否有保存数据,这里是用一个字符串来记录
keys := fmt.Sprintf("%d%s%d",m,",",n)
if v,ok := memory[keys];ok {
return v
}
if m == 1 && n == 1 {
return 1
//列出直走1步的情况
} else if (m == 1 && n == 2) || ( m == 2 && n == 1) {
return 1
//列出完结情况
} else if m < 0 || n < 0 {
return 0
}
//记录每次算出过的值
memory[keys] = parton(m, n - 1, memory) + parton(m - 1, n, memory)
//构造递归算法
//通过map 来记录每一个目标需要的步数
return memory[keys]
}
//动态规划写法
func uniquePaths(m int, n int) int {
//先定义一个二维的切片,注意二维数组不支持动态的长度,所以只能先建一个m+1长度的二维切片,再定义一维的长度,这里使用n+1,是因为,我这边默认从 1开始,1-1是第一个位置,而不是默认的0-0,不加1会越界
//var memory [10][10]int
memory := make([][]int, m+1)
//请一定注意 我这边是1-1...m-n,所以最终也是 return memory[m][n]
//考虑n = 1的情况,确定好二维切片第二部分的长度
for i:=1; i<=m; i++{
memory[i] = make([]int, n+1)
memory[i][1] = 1
}
//先考虑m = 1的情况
for i:=1; i<=n; i++{
memory[1][i] = 1
}
//可以先先把这个基准的关系列出来,现在看还缺一些边界值,如上
for i:= 2; i<= m; i++ {
for j:= 2; j<= n;j++{
memory[i][j] = memory[i-1][j] + memory[i][j-1]
}
}
return memory[m][n]
}
三:不同路径障碍
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
示例 1:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
示例 2:
输入:obstacleGrid = [[0,1],[0,0]]
输出:1
func uniquePathsWithObstacles(obstacleGrid [][]int) int {
//这里使用滚动数组,「滚动数组思想」是一种常见的动态规划优化方法,当我们定义的状态在动态规划的转移方程中只和某几个状态相关的时候,就可以考虑这种优化方法,目的是给空间复杂度「降维」,比如这道题,每个目标值都可以由左上两个值得到,所以就可以是,f(5) = f(4) + f(3)
//获取m n
m, n := len(obstacleGrid), len(obstacleGrid[0])
var memroy = make([]int, n)
//初始位置不是障碍物,默认1
if obstacleGrid[0][0] == 0 {
memroy[0] = 1
}
for i:= 0; i < m; i++ {
for j:= 0; j < n; j++ {
//障碍物 记作0 计算下一个
if obstacleGrid[i][j] == 1 {
memroy[j] = 0
continue
}
//obstacleGrid[i][j-1] == 0 这一步的判断是因为,如果左边有障碍,其实f(j)的值不会变,就等于顶部的值
if j-1 >= 0 && obstacleGrid[i][j-1] == 0 {
//加上这个注释降维就理解了
//fmt.Println("i,j",i,j)
//fmt.Println(j,memroy[j])
memroy[j] += memroy[j - 1]
//fmt.Println(j,memroy[j])
}
}
}
return memroy[len(memroy) - 1]
}