动态规划-01背包和完全背包

01背包
01背包模板

func zeroOneKnapsack(weights []int, values []int, capacity int) int {
    n := len(weights)
    // dp[i][j] 表示:考虑前 i 个物品(索引从 0 到 i-1),容量为 j 时的最大价值
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, capacity+1)
    }

    // i 从 0 开始,表示我们现在要处理第 i 个物品(索引为 i)
    for i := 0; i < n; i++ {
        for j := 0; j <= capacity; j++ {
            // 不选第 i 个物品(索引为 i)
            dp[i+1][j] = dp[i][j]

            // 尝试选择第 i 个物品(索引为 i)
            if j >= weights[i] { // 直接使用 weights[i],因为 i 就是索引
                // 选择的话,价值是:前 i 个物品在容量 j-weights[i] 下的最大价值 + 第 i 个物品的价值
                dp[i+1][j] = max(dp[i+1][j], dp[i][j-weights[i]] + values[i])
            }
        }
    }

    // dp[n][capacity] 仍然是最终答案:考虑所有 n 个物品(索引 0 到 n-1),容量为 capacity 时的最大价值
    return dp[n][capacity]
}

01背包变体
416. 分割等和子集

func canPartition(nums []int) bool {
    n := len(nums)
    sum := 0
    for _, num := range nums {
        sum += num
    }

    if sum % 2 == 1 {
        return false
    }
    target := sum / 2
    dp := make([][]bool, n + 1)
    for i := 0; i <= n; i ++ {
        dp[i] = make([]bool, target + 1)
    }
    dp[0][0] = true

    for i := 0; i < n; i ++ {
        for j := 0; j <= target; j ++ {
            dp[i + 1][j] = dp[i][j]
            if j >= nums[i] {
                dp[i + 1][j] = dp[i + 1][j] || dp[i][j - nums[i]]
            }
        }
    }
    return dp[n][target]
}

完全背包
完全背包模板

func completeKnapsack(weights []int, values []int, capacity int) int {
    n := len(weights)
    // dp[i][j] 表示:考虑前 i 个物品(索引从 0 到 i-1),容量为 j 时的最大价值
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, capacity+1)
    }

    // i 从 0 开始,表示我们现在要处理第 i 个物品(索引为 i)
    for i := 0; i < n; i++ {
        // 对于当前的 i 个物品,枚举背包所有容量的可能性
        for j := 0; j <= capacity; j++ {
            // 不选第 i 个物品(索引为 i)
            dp[i+1][j] = dp[i][j]

            // 尝试选择第 i 个物品(索引为 i)
            // 如果当前背包容量 j 能装得下第 i 个物品(其重量为 weights[i]),那么我们可以尝试“选择这个物品”
            if j >= weights[i] {
                // 关键区别:完全背包用 dp[i][j-weights[i]],而不是 dp[i-1][j-weights[i]]
                // 因为可以选择无限次,所以仍然考虑前 i 个物品(包括当前物品)
                dp[i+1][j] = max(dp[i+1][j], dp[i+1][j-weights[i]] + values[i])
            }
        }
    }

    return dp[n][capacity]
}

完全背包变体
322. 零钱兑换

func coinChange(coins []int, amount int) int {
    n := len(coins)

    dp := make([][]int, n + 1)
    for i := 0; i <= n; i++ {
        dp[i] = make([]int, amount + 1)
        for j := 0; j <= amount; j ++ {
            dp[i][j] = amount + 1
        }
    }
    dp[0][0] = 0

    for i := 0; i < n; i ++ {
        for j := 0; j <= amount; j ++ {
            dp[i + 1][j] = dp[i][j]
            if j >= coins[i] {
                dp[i + 1][j] = min(dp[i + 1][j], dp[i + 1][j - coins[i]] + 1)
            }
        }
    }
    res := dp[n][amount]
    if res > amount {
        return -1
    }
    return res
}
  1. 完全平方数
func numSquares(n int) int {
    // 生成所有平方数 <= n
    var squares []int
    i := 1
    for i*i <= n {
        squares = append(squares, i*i)
        i++
    }

    // 完全背包函数
    return completeKnapsack(squares, n)
}

func completeKnapsack(squares []int, capacity int) int {
    n := len(squares)
    // dp[i][j] 表示前 i 个平方数中,凑出总和为 j 的最小平方数个数
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, capacity+1)
        for j := range dp[i] {
            dp[i][j] = capacity + 1 // 初始化为一个大值
        }
    }
    dp[0][0] = 0 // 初始状态:0 个平方数凑出 0

    for i := 0; i < n; i++ {
        for j := 0; j <= capacity; j++ {
            // 不选当前平方数
            dp[i+1][j] = dp[i][j]

            // 选当前平方数(完全背包允许重复使用)
            if j >= squares[i] {
                dp[i+1][j] = min(dp[i+1][j], dp[i+1][j - squares[i]] + 1)
            }
        }
    }

    return dp[n][capacity]
}
posted @ 2025-08-01 16:34  wushucan  阅读(3)  评论(0)    收藏  举报