动态规划算法
动态规划算法思想概括
自底向上的递推算法,通过求解子问题,逐步得到原问题的解。
状态转移方程
状态定义
状态是指问题的一个特定阶段的解的情况。比如,对于背包问题,状态就是当前背包的容量和已经放入背包的物品。
状态转移
确定如何从一个状态转移到另一个状态。比如,在背包问题中,对于第 i 个物品,可以选择放入背包或不放入背包,然后根据这两种选择的结果来决定最终的状态值。
算法框架
- 定义状态
- 确定状态转移方程
- 确定初始条件和边界条件
- 确定执行顺序
- 返回最终结果
经典题目
斐波那契数列
后一个数等于前两个数的和。
func fib(n int) {
if n <= 1 {
fmt.Println(n)
}
dp := make([]int, n+1)
dp[0] = 0
dp[1] = 1
for i := 2; i <= n; i++ {
dp[i] = dp[i-1] + dp[i-2]
fmt.Println(dp[i])
}
fmt.Println(dp[n])
}
func main() {
fib(10)
}
爬楼梯
每次可以爬 1 或 2 个台阶,求爬到第 n 阶楼梯的方法数。
func climb(n int) {
if n <= 2 {
fmt.Println(n)
}
dp := make([]int, n+1)
dp[1] = 1
dp[2] = 2
for i := 3; i <= n; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
fmt.Println(dp[n])
}
func main(){
climb(10)
}
01背包问题
有 n 个物品,每个物品只有一个。
每个的重量为 w[i],价值为 v[i],背包的容量为 W。求在不超过背包容量的情况下,背包中物品的最大总价值。
|
func main() {
weights := []int{1, 2, 3, 4}
values := []int{10, 20, 60, 40}
W := 5
maxValue := knapsack(weights, values, W)
fmt.Printf("The maximum value is: %d\n", maxValue)
}
/*
容量 0 1 2 3 4 5
物品0 [0 0 0 0 0 0]
物品1 [0 0 0 0 0 0]
物品2 [0 0 0 0 0 0]
物品3 [0 0 0 0 0 0]
物品4 [0 0 0 0 0 0]
*/
func knapsack(w []int, v []int, W int) int {
// 定义dp数组,dp[i][j]表示前i个物品放入容量为j的背包中的最大价值
dp := make([][]int, len(w)+1)
for i := range dp {
dp[i] = make([]int, W+1)
}
// 遍历物品
for i := 1; i <= len(w); i++ {
// 遍历背包容量
for j := 0; j <= W; j++ {
// 记当前物品在原有数组中的下标
index := i - 1
if j < w[index] {
// 当前物品重量大于此时背包容量,不能放入背包
dp[i][j] = dp[i-1][j]
} else {
// 当前物品重量小于等于背包容量,可以选择放入或不放入背包
// 因为当前物品只有一个,只能从前面i-1个物品中取。
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[index]]+v[index])
}
}
}
return dp[len(w)][W]
}
完全背包问题
有 n 个物品,每个物品有无限个。
每个的重量为 w[i],价值为 v[i],背包的容量为 W。求在不超过背包容量的情况下,背包中物品的最大总价值。
与01背包问题相区别的是状态转移方程:
01背包:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[index]] + v[index])
完全背包:dp[i][j] = max(dp[i-1][j], dp[i][j-w[index]] + v[index])
最长公共子列
给定两个序列 X 和 Y,求 X 和 Y 的最长公共子列的长度。
func main() {
X := []int{1, 2, 3, 4, 5}
Y := []int{2, 4, 6, 8, 10}
lcs := longestCommonSubsequence(X, Y)
fmt.Printf("The length of the longest common subsequence is: %d\n", lcs)
}
func longestCommonSubsequence(X []int, Y []int) int {
m, n := len(X), len(Y)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if X[i-1] == Y[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
}
}
}
return dp[m][n]
}
最小路径和
给定一个 m x n 的矩阵,从左上角到右下角的最小路径和,每次只能向右或向下移动。
func main() {
matrix := [][]int{
{1, 3, 1},
{1, 5, 1},
{4, 2, 1},
}
minPathSum := minPathSum(matrix)
fmt.Printf("The minimum path sum is: %d\n", minPathSum)
}
func minPathSum(matrix [][]int) int {
m, n := len(matrix), len(matrix[0])
dp := make([][]int, m)
for i := range dp {
dp[i] = make([]int, n)
}
// 初始化第一行和第一列
dp[0][0] = matrix[0][0]
for i := 1; i < m; i++ {
dp[i][0] = dp[i-1][0] + matrix[i][0]
}
for j := 1; j < n; j++ {
dp[0][j] = dp[0][j-1] + matrix[0][j]
}
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + matrix[i][j]
}
}
return dp[m-1][n-1]
}