背包问题中“遍历方向”与“内外循环”对结果的影响总结

核心关系表

问题类型 内外循环顺序 背包容量遍历方向 结果特征(核心影响) 典型题目 特殊案例验证(错误vs正确)
01背包(物品不可重复) 物品外循环 逆序(从大到小) 每个物品仅用1次,不考虑选择顺序 分割等和子集、目标和 若正序遍历,会重复选物品(如nums=[2], target=4会误判为可分割)
完全背包-组合 物品外循环 正序(从小到大) 物品可重复用,不考虑顺序(组合唯一) 零钱兑换II(凑数方法数) 若背包外循环,会多算排列(如coins=[1,2], amount=3会从2种→3种)
完全背包-排列 背包外循环 正序(从小到大) 物品可重复用,考虑顺序(排列不同) 组合总和IV(凑数方法数) 若物品外循环,会少算排列(如nums=[1,2], target=3会从4种→2种)
完全背包-最值 内外循环均可 正序(从小到大) 物品可重复用,与顺序无关(最值唯一) 零钱兑换(最少硬币数) 无论内外循环,结果一致(如coins=[1,2,5], amount=11均返回3)

结合特殊案例详解

一、01背包(物品不可重复使用)

核心逻辑:每个物品只能选1次,需通过“物品外循环+背包逆序遍历”避免重复使用。

特殊案例:nums=[2,3,5],判断能否分割出和为5的子集(正确答案:能,选5或2+3)
  • 错误做法(正序遍历背包)
    处理num=2时,正序遍历j=2→5,会更新dp[2]=True,进而在j=4时用dp[2]更新dp[4]=True(相当于选了两次2),最终错误判断“能分割出和为4的子集”(实际nums中只有1个2)。

  • 正确做法(逆序遍历背包)

    def canPartition(nums):
        target = 5
        dp = [False] * (target + 1)
        dp[0] = True
        for num in nums:
            # 逆序遍历:从大到小,避免同一物品被重复选
            for j in range(target, num - 1, -1):
                dp[j] = dp[j] or dp[j - num]
        return dp[5]  # 返回True(正确)
    

    逆序遍历确保每个物品仅被使用1次,如处理num=2时,j=2更新dp[2],但j=4不会再用dp[2]的新值(因j=4在j=2之前遍历),避免重复选择。

二、完全背包-组合(物品可重复,不考虑顺序)

核心逻辑:物品可重复使用,通过“物品外循环+背包正序遍历”实现重复使用,且保证组合顺序不影响结果。

特殊案例:coins=[1,2],amount=3(正确组合数:2种:1+1+1、1+2)
  • 错误做法(背包外循环)
    先遍历金额j=1→3,再遍历硬币,会将“1+2”和“2+1”视为两种组合,最终结果为3(错误)。

  • 正确做法(物品外循环)

    def change(amount, coins):
        dp = [0] * (amount + 1)
        dp[0] = 1
        for coin in coins:  # 先固定硬币,保证顺序唯一
            for j in range(coin, amount + 1):
                dp[j] += dp[j - coin]
        return dp[3]  # 返回2(正确)
    

    物品外循环确保“先1后2”的固定顺序,不会出现“先2后1”的重复计数,组合数正确。

三、完全背包-排列(物品可重复,考虑顺序)

核心逻辑:物品可重复使用,通过“背包外循环+背包正序遍历”实现重复使用,且保证顺序不同视为不同结果。

特殊案例:nums=[1,2],target=3(正确排列数:4种:1+1+1、1+2、2+1)
  • 错误做法(物品外循环)
    先遍历物品1再遍历物品2,会将“1+2”和“2+1”视为同一种,最终结果为2(错误)。

  • 正确做法(背包外循环)

    def combinationSum4(nums, target):
        dp = [0] * (target + 1)
        dp[0] = 1
        for j in range(1, target + 1):  # 先固定金额,再试所有物品
            for num in nums:
                if j >= num:
                    dp[j] += dp[j - num]
        return dp[3]  # 返回4(正确)
    

    背包外循环允许每个金额下尝试所有物品,自然包含“1+2”和“2+1”两种顺序,排列数正确。

四、完全背包-最值(特殊情况:顺序不影响结果)

核心逻辑:求“最少/最多”等最值时,因顺序不影响结果,内外循环可互换,只需保证正序遍历金额。

特殊案例:coins=[1,2,5],amount=11(正确答案:3)
  • 物品外循环

    for coin in coins:
        for j in range(coin, amount + 1):
            dp[j] = min(dp[j], dp[j - coin] + 1)  # 结果3(正确)
    
  • 金额外循环

    for j in range(1, amount + 1):
        for coin in coins:
            if j >= coin:
                dp[j] = min(dp[j], dp[j - coin] + 1)  # 结果仍为3(正确)
    

原因:最值问题只关心“是否存在更少/更多的数量”,而不关心“用了哪些顺序”,因此循环顺序不影响结果。

总结:核心判断依据

  1. 是否允许重复使用物品

    • 逆序遍历背包 → 01背包(如分割等和子集,避免重复选2);
    • 正序遍历背包 → 完全背包(如零钱兑换,允许重复用2元)。
  2. 是否考虑顺序(仅影响“计数类”问题)

    • 物品外循环 → 组合(如零钱兑换II,1+2和2+1算1种);
    • 背包外循环 → 排列(如组合总和IV,1+2和2+1算2种);
    • 最值类问题(如最少硬币数) → 顺序无关,循环可互换。

跟我一起念:
物品只能用1次的,逆序遍历;跟顺序有关的,背包在外循环!
物品只能用1次的,逆序遍历;跟顺序有关的,背包在外循环!
物品只能用1次的,逆序遍历;跟顺序有关的,背包在外循环!