- 最优子结构:大问题的最优解包含子问题的最优解
- 重叠子问题:子问题被重复计算
- 状态转移方程:递推关系
- 定义状态(dp数组含义)
- 初始化
- 状态转移方程
- 遍历顺序
- 返回结果
n个物品,背包容量W,每个物品有重量w[i]和价值v[i],求最大价值。
func knapsack(weights, values []int, capacity int) int {
n := len(weights)
// dp[i][j]: 前i个物品,容量为j的最大价值
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, capacity+1)
}
for i := 1; i <= n; i++ {
for j := 0; j <= capacity; j++ {
// 不选第i个物品
dp[i][j] = dp[i-1][j]
// 选第i个物品
if j >= weights[i-1] {
dp[i][j] = max(dp[i][j],
dp[i-1][j-weights[i-1]] + values[i-1])
}
}
}
return dp[n][capacity]
}func knapsackOptimized(weights, values []int, capacity int) int {
dp := make([]int, capacity+1)
for i := 0; i < len(weights); i++ {
// 逆序遍历,避免重复使用
for j := capacity; j >= weights[i]; j-- {
dp[j] = max(dp[j], dp[j-weights[i]] + values[i])
}
}
return dp[capacity]
}时间复杂度:O(n × capacity)
空间复杂度:O(capacity)
func longestCommonSubsequence(text1, text2 string) int {
m, n := len(text1), len(text2)
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 text1[i-1] == text2[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]
}func maxProfit(prices []int) int {
if len(prices) == 0 {
return 0
}
minPrice := prices[0]
maxProfit := 0
for _, price := range prices {
if price < minPrice {
minPrice = price
} else {
maxProfit = max(maxProfit, price-minPrice)
}
}
return maxProfit
}func maxProfit2(prices []int) int {
profit := 0
for i := 1; i < len(prices); i++ {
if prices[i] > prices[i-1] {
profit += prices[i] - prices[i-1]
}
}
return profit
}func maxProfit3(k int, prices []int) int {
if len(prices) == 0 {
return 0
}
n := len(prices)
// dp[i][j][0]: 第i天,交易j次,不持股
// dp[i][j][1]: 第i天,交易j次,持股
dp := make([][][2]int, n)
for i := range dp {
dp[i] = make([][2]int, k+1)
}
// 初始化
for j := 0; j <= k; j++ {
dp[0][j][0] = 0
dp[0][j][1] = -prices[0]
}
for i := 1; i < n; i++ {
for j := 1; j <= k; j++ {
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]+prices[i])
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i])
}
}
return dp[n-1][k][0]
}func rob(nums []int) int {
if len(nums) == 0 {
return 0
}
if len(nums) == 1 {
return nums[0]
}
// dp[i]: 偷前i个房子的最大金额
prev2, prev1 := 0, nums[0]
for i := 2; i <= len(nums); i++ {
current := max(prev1, prev2+nums[i-1])
prev2 = prev1
prev1 = current
}
return prev1
}func climbStairs(n int) int {
if n <= 2 {
return n
}
prev2, prev1 := 1, 2
for i := 3; i <= n; i++ {
current := prev1 + prev2
prev2 = prev1
prev1 = current
}
return prev1
}func minDistance(word1, word2 string) int {
m, n := len(word1), len(word2)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
// 初始化
for i := 0; i <= m; i++ {
dp[i][0] = i
}
for j := 0; j <= n; j++ {
dp[0][j] = j
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if word1[i-1] == word2[j-1] {
dp[i][j] = dp[i-1][j-1]
} else {
dp[i][j] = min(
dp[i-1][j]+1, // 删除
dp[i][j-1]+1, // 插入
dp[i-1][j-1]+1, // 替换
)
}
}
}
return dp[m][n]
}
func min(nums ...int) int {
result := nums[0]
for _, num := range nums {
if num < result {
result = num
}
}
return result
}关键要点:
- ✅ 动态规划 = 递归 + 记忆化
- ✅ 关键是找到状态转移方程
- ✅ 空间优化:滚动数组
- ✅ 背包、LCS、股票、打家劫舍是经典问题
- 《算法竞赛进阶指南》
- 动态规划专题 - LeetCode
💡 思考题:
- 如何判断一个问题可以用动态规划?
- 背包问题为什么要逆序遍历?
- 如何优化动态规划的空间复杂度?