You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

回溯法在LeetCode #322(零钱兑换)出错却在#78(子集)正确的原因

问题背景

LeetCode #322 零钱兑换

给定一个表示不同面额硬币的整数数组coins和一个表示总金额的整数amount。返回凑成总金额所需的最少硬币个数。如果无法凑出该金额,返回-1。你可以认为每种硬币的数量是无限的。

示例1:
输入:coins = [1,2,5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

import java.util.Arrays;
import java.util.ArrayList;

class Solution {
    static final int MAX_INF = Integer.MAX_VALUE;

    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        for (int[] arr : dp) {
            Arrays.fill(arr, -1);
        }
        int res = minCoinsReq(coins, amount, n,new ArrayList<Integer>(), dp);
        if (res == MAX_INF -1) {
            return -1;
        }
        return res;
    }

  public int minCoinsReq(int[] coins, int amount, int n, ArrayList<Integer> curCoinsList, int[][] dp){  
     if(amount ==0){
        return curCoinsList.size();
     } 
       if(n==0){
        return MAX_INF-1;
    }
    if(dp[n][amount]!=-1 ){
        return dp[n][amount];
    }
    if(coins[n-1]<=amount){
        int coinAtIdxNotIncluded = minCoinsReq(coins,amount,n-1,curCoinsList,dp);
        curCoinsList.add(coins[n-1];)
        int coinAtIdxIncluded = minCoinsReq(coins,amount-coins[n-1],n, curCoinsList,dp);
        curCoinsList.remove(curCoinsList.size()-1);
        return dp[n][amount]= Math.min(coinAtIdxIncluded,coinAtIdxNotIncluded);
    }else {
         return dp[n][amount]= minCoinsReq(coins,amount,n-1, curCoinsList,dp);
      
    }
   }
}

LeetCode #78 子集

给定一个元素互不相同的整数数组nums,返回其所有可能的子集(幂集)。解集不能包含重复的子集,返回顺序任意。

示例1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

class Solution {
public List<List<Integer>> subsets(int[] nums) {
    Set<List<Integer>> result = new HashSet<>();
    addSubsetsRec(nums, result, new ArrayList<Integer>(), 0);
    return new ArrayList<>(result);
}

public void addSubsetsRec(int[] nums, Set<List<Integer>> result, List<Integer> currentList, int idx) {
    if (idx == nums.length) {
        result.add(new ArrayList<>(currentList));
        return;
    }
    
    // Exclude current element
    addSubsetsRec(nums, result, currentList, idx + 1);

    // Include current element
    currentList.add(nums[idx]);
    addSubsetsRec(nums, result, currentList, idx + 1);

    // Backtrack to remove the recently added element
    currentList.remove(currentList.size() - 1);
}
}

技术问询

为何使用回溯法实现上述两个问题时,在LeetCode #322(零钱兑换)问题中得到错误结果,而在LeetCode #78(子集)问题中结果正确?


问题分析与解答

核心差异:回溯状态管理的逻辑适配性

子集问题(#78)的回溯逻辑是遍历所有元素的选择组合,每次递归到终点时,都会把当前currentList的副本存入结果集。虽然递归过程中共享同一个currentList,但回溯时会恢复列表状态(移除刚添加的元素),且每个分支的结果都是独立的副本,不同分支之间不会互相干扰,因此能正确生成所有子集。

而零钱兑换问题(#322)的代码存在三个关键问题,导致结果错误:

  1. 共享列表的状态污染与记忆化冲突
    递归中传递的curCoinsList是同一个ArrayList引用,同时还使用dp数组做记忆化。当计算选择当前硬币的分支时,会给curCoinsList添加元素,递归返回后再移除,但dp[n][amount]存储的是该分支返回的curCoinsList.size()——这个值依赖于当时的列表状态。后续其他递归分支访问同一个dp[n][amount]时,列表状态已经被修改,拿到的记忆化值完全错误。

  2. 语法错误
    代码中curCoinsList.add(coins[n-1];)这一行多了一个分号,正确写法应为curCoinsList.add(coins[n-1]);,这个错误会直接导致编译失败,无法运行出正确结果。

  3. 记忆化设计逻辑错误
    零钱兑换的dp[n][amount]应该直接存储“用前n种硬币凑成amount的最小硬币数”,不需要依赖外部共享列表统计数量。正确的做法是:选择当前硬币时,递归结果加1(代表加上当前这枚硬币);不选则直接取递归结果,最后取两者最小值存入dp。

修正后的零钱兑换递归实现(参考)

去掉共享列表,直接通过递归返回值计算最小硬币数:

import java.util.Arrays;

class Solution {
    static final int MAX_INF = Integer.MAX_VALUE;

    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        for (int[] arr : dp) {
            Arrays.fill(arr, -1);
        }
        int res = minCoinsReq(coins, amount, n, dp);
        return res == MAX_INF - 1 ? -1 : res;
    }

    public int minCoinsReq(int[] coins, int amount, int n, int[][] dp) {
        if (amount == 0) {
            return 0; // 凑成0元需要0个硬币
        }
        if (n == 0) {
            return MAX_INF - 1; // 无硬币可选,无法凑成
        }
        if (dp[n][amount] != -1) {
            return dp[n][amount];
        }
        if (coins[n-1] <= amount) {
            // 选择当前硬币:递归结果+1
            int include = minCoinsReq(coins, amount - coins[n-1], n, dp);
            if (include != MAX_INF - 1) {
                include += 1;
            }
            // 不选择当前硬币
            int exclude = minCoinsReq(coins, amount, n-1, dp);
            return dp[n][amount] = Math.min(include, exclude);
        } else {
            // 硬币面额超过金额,只能不选
            return dp[n][amount] = minCoinsReq(coins, amount, n-1, dp);
        }
    }
}

内容的提问来源于stack exchange,提问作者Ashna kohli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 07:15:55