递归解决有限硬币组合求和问题:优化与常见陷阱

递归解决有限硬币组合求和问题:优化与常见陷阱

本文探讨如何使用递归解决有限硬组合求和问题,即判断给定一组只能使用一次的硬币能否凑成特定目标金额。我们将分析原始实现中的数组复制错误和效率问题,并提出一种基于“包含或排除”策略的优化递归方案,显著提升代码的清晰度和性能,同时强调递归解法中的关键考量点。

问题描述:有限硬币组合求和

“有限硬币组合求和”问题要求我们判断,给定一组面额各异的硬币(每种硬币只能使用一次),能否凑成一个特定的目标总和。例如,给定硬币 {1, 5, 16} 和目标金额 6,我们可以用 1 + 5 凑成,因此结果为真。如果目标金额是 8,则无法凑成,结果应为假。这是一个典型的子集和问题变种,通常可以通过递归或动态规划解决。

原始递归尝试与常见陷阱

在尝试解决此类问题时,初学者常会采用一种直观的递归思路:遍历所有硬币,对于当前硬币,如果目标金额大于或等于它,就尝试将其包含在内,然后递归处理剩余硬币和减去当前硬币后的目标金额。

然而,在这种实现中,存在两个常见的陷阱:

数组复制错误: 在递归调用中,为了模拟“使用一次”的限制,需要将当前硬币从硬币列表中移除。如果手动复制数组,很容易出现索引错误。例如,原始代码中的 red[it] = coins[i] 是一个典型的错误。在构建新数组 red 时,意图是复制除 coins[i] 之外的所有元素,但正确的做法应该是复制 coins[x],其中 x 是遍历原始 coins 数组的索引。即 red[it] = coins[x] 才是正确的复制方式。效率问题: 每次递归调用都通过循环遍历硬币数组,并在循环内部创建一个新的、长度减一的数组。这种做法不仅增加了代码的复杂性,也带来了显著的性能开销。每次递归层级都会进行不必要的数组创建和元素复制,导致时间复杂度远超预期。

考虑以下原始代码片段中的错误示例:

// 错误示例:数组复制逻辑有误for (int i = 0; i = coins[i]) {        int[] red = new int[coins.length - 1];        int it = 0;        for(int x = 0; x < coins.length; x++){            if(!(i == x)){                // 错误:应该复制 coins[x],而不是 coins[i]                red[it] = coins[i]; // 此处应为 red[it] = coins[x];                it += 1;            }        }        ans = go(red, goal - coins[i]);    }}

这个错误会导致新数组 red 中填充的都是被跳过的 coins[i] 的值,而不是原始数组中其他硬币的值,从而产生不正确的结果。

优化后的递归策略:包含或排除

解决此类问题的更优雅且高效的递归方法是采用“包含或排除”策略。对于当前考虑的硬币(通常是数组的第一个元素),我们有两种选择:

不包含当前硬币: 我们跳过当前硬币,直接递归处理剩余的硬币和不变的目标金额。包含当前硬币: 我们使用当前硬币,然后递归处理剩余的硬币和减去当前硬币面额后的目标金额。

只要这两种情况中的任何一种能够成功凑成目标金额,那么总和就是可达的。这种方法避免了复杂的循环和手动数组复制,而是通过递归参数的巧妙设计来处理子问题。

核心思想:

基本情况 (Base Cases):如果目标金额 goal 为 0,说明已经成功凑成,返回 true。如果硬币列表 coins 为空或者目标金额 goal 小于 0(说明超出了目标),则无法凑成,返回 false。递归步骤 (Recursive Step):取出当前硬币 coins[0]。创建新的硬币列表 tailOfCoins,包含 coins 中除 coins[0] 之外的所有硬币。递归调用 go(tailOfCoins, goal):表示不使用 coins[0],尝试用剩余硬币凑成 goal。递归调用 go(tailOfCoins, goal – coins[0]):表示使用 coins[0],尝试用剩余硬币凑成 goal – coins[0]。如果上述任一调用返回 true,则最终结果为 true。

这种方法的优势在于:

清晰简洁: 逻辑更易于理解和实现。避免手动复制错误: 利用 Arrays.copyOfRange 等工具函数可以安全高效地创建子数组。效率提升: 虽然时间复杂度仍为指数级 (O(2^N),其中 N 是硬币数量),但它避免了在每次循环迭代中重复创建数组,从而减少了常数因子,提高了实际运行效率。

核心代码实现

以下是采用“包含或排除”策略的优化递归实现:

import java.util.Arrays; // 引入 Arrays 类用于数组操作public class FiniteCoinsSum {    /**     * 判断给定一组硬币(每枚硬币只能使用一次)能否凑成目标金额。     *     * @param coins 硬币面额数组。     * @param goal 目标金额。     * @return 如果能凑成目标金额,返回 true;否则返回 false。     */    public static boolean canMakeSum(int[] coins, int goal) {        // 基本情况 1: 如果目标金额为0,说明已经成功凑成。        if (goal == 0) {            return true;        }        // 基本情况 2:        // 如果硬币列表为空(没有硬币可用),或者目标金额小于0(超出了目标),        // 则无法凑成。        if (coins.length == 0 || goal  " + canMakeSum(coins1, goal1)); // 预期: true        int[] coins2 = {111, 1, 2, 3, 9, 11, 20, 30};        int goal2 = 8; // 无法凑成 8        System.out.println("Coins: " + Arrays.toString(coins2) + ", Goal: " + goal2 + " -> " + canMakeSum(coins2, goal2)); // 预期: false        int[] coins3 = {2, 3, 5};        int goal3 = 7; // 2 + 5 = 7        System.out.println("Coins: " + Arrays.toString(coins3) + ", Goal: " + goal3 + " -> " + canMakeSum(coins3, goal3)); // 预期: true        int[] coins4 = {10, 20, 30};        int goal4 = 5; // 无法凑成        System.out.println("Coins: " + Arrays.toString(coins4) + ", Goal: " + goal4 + " -> " + canMakeSum(coins4, goal4)); // 预期: false        int[] coins5 = {1, 2, 3};        int goal5 = 0; // 目标为0,直接返回true        System.out.println("Coins: " + Arrays.toString(coins5) + ", Goal: " + goal5 + " -> " + canMakeSum(coins5, goal5)); // 预期: true    }}

注意事项与总结

递归基的准确性: 正确定义递归的终止条件至关重要。goal == 0 是成功条件,而 coins.length == 0 || goal < 0 是失败条件。数组的不可变性与子数组创建: 在递归中传递数组时,通常需要确保每次递归调用都处理一个“新”的子问题状态。使用 Arrays.copyOfRange 可以方便地创建子数组,避免原始数组被修改,这对于递归的正确性至关重要。时间复杂度: 尽管优化后的代码更简洁,但其时间复杂度仍为指数级 (O(2^N)),对于大规模的硬币数量 N,性能可能成为瓶颈。在这种情况下,可以考虑使用动态规划(背包问题变种)来优化到伪多项式时间复杂度。问题建模: 许多组合问题都可以抽象为“包含或排除”某个元素,然后递归解决子问题。熟练掌握这种思维模式有助于解决多种类似问题。

通过采用这种优化的递归策略,我们不仅修复了原始代码中的数组复制错误,还显著提升了代码的清晰度和可维护性,为解决有限硬币组合求和问题提供了一个高效且易于理解的递归解决方案。

以上就是递归解决有限硬币组合求和问题:优化与常见陷阱的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/67135.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月12日 10:15:54
下一篇 2025年11月12日 10:55:38

相关推荐

  • 什么是谐波形态交易?利用加特利与蝴蝶形态预测反转点

    谐波形态交易通过斐波那契比率识别反转区域,结合加特利与蝴蝶形态定位买卖点。1、加特利形态要求B点回撤XA的61.8%,D点达XA的78.6%回撤且CD为AB的1.272倍;2、蝴蝶形态B点需回撤XA的78.6%,D点延伸至XA的127%-161.8%扩展位,CD为AB的1.618-2.618倍;3、…

    2025年12月11日
    000
  • Solana为何被称为“高性能公链”?一文对比其技术优势与挑战

    Solana凭借PoH、Sealevel、Gulf Stream等技术创新实现高吞吐低延迟,通过优化垃圾防护、QoS和激励小型节点提升稳定性与去中心化水平。 Solana被称为“高性能公链”,主要因其在交易处理速度、成本和架构设计上的显著优势,同时面临网络稳定性与去中心化程度的挑战。 为了方便新手快…

    2025年12月11日
    000
  • 详解跨链通信协议(IBC):Cosmos生态的底层技术魔法

    IBC通过轻客户端和默克尔证明实现跨链通信,需先建立连接并部署轻客户端,再创建通道传输数据,中继器负责传递数据包并验证,通信失败时可通过重启中继、超时处理、时间同步和更新轻客户端修复。 跨链通信协议(IBC)是Cosmos生态实现区块链互操作性的核心技术,使不同链之间可安全传递数据与资产。 为了方便…

    2025年12月11日
    000
  • 什么是Stacks (STX)?为比特币带来智能合约功能

    Stacks(STX)是基于比特币的第二层协议,通过PoX共识机制实现智能合约与dApps功能,用户质押STX可获比特币奖励,交易在比特币链上结算;开发者使用Clarity语言构建安全智能合约,并可读取比特币数据;Stacks支持比特币参与DeFi,如在ALEX或Arkadiko平台抵押BTC借贷资…

    2025年12月11日
    000
  • Move语言是什么?为何Aptos和Sui选择它作为开发语言

    Move语言专为区块链设计,强调安全与资源管理。其线性类型系统防止资产复制或丢失,字节码验证确保执行前安全,模块化结构保护数据。Aptos采用Move因继承Diem技术、具备高安全性与可升级合约。Sui则基于Move构建原生对象模型,引入所有权规则实现并行执行,优化Gas消耗,提升吞吐量。 Move…

    2025年12月11日
    000
  • 什么是VPVR成交量分布图?寻找筹码密集区作为合约攻防线

    VPVR成交量分布图通过分析价格与成交量关系,定位关键支撑阻力。其核心由POC(最高成交量价)和VA(70%交易区间,边界为VAH/VAL)构成,反映市场共识区域。启用该指标后,横向条带长度揭示成交密集度,最长处为POC,代表多空重心;VA区域则显示主要交易带,是判断方向的关键。单峰形态表明窄幅换手…

    2025年12月11日
    000
  • 区块链浏览器到底怎么用?人人都能成为链上侦探

    通过区块链浏览器可查询地址、交易等公开信息,结合插件提升分析效率,并利用专业平台实现跨链追踪与风险识别。 区块链浏览器是查询链上数据的核心工具,用户可通过它追踪交易、分析地址行为。 为了方便新手快速上手币圈交易并实时查看市场数据,可通过主流交易所币安(Binance)或欧易OKX注册账户并使用官方A…

    2025年12月11日
    000
  • 什么是“均值回归”?在加密市场中寻找交易机会

    均值回归理论认为资产价格会从极端水平回归长期均值。首先通过90日收盘价计算SMA和标准差,结合布林带识别超买(均值+2倍标准差以上)或超卖(均值-2倍标准差以下)状态;其次引入RSI和随机震荡指标确认动能,RSI连续三天高于70为超买,低于30为超卖,K线与D线在80以上死叉为卖出信号,在20以下金…

    2025年12月11日
    000
  • 复利思维有多可怕?坚持这套策略三年资产翻十倍!

    复利思维通过持续再投资实现资产指数增长,需选择高潜力加密资产、利用DeFi自动化复投,并动态调整持仓以优化收益。 Binance币安 欧易OKX ️ Huobi火币️ gateio芝麻   复利思维的核心在于持续增长和时间积累,通过将收益再投资,实现资产的指数级膨胀。 一、选择高增长潜力的加密资产 …

    2025年12月11日
    000
  • 币圈新人入场指南,手把手教你从0到1赚第一桶金!

    掌握基础概念、选择交易平台、实施安全防护、执行首次买卖。1、理解区块链、主流币种及DeFi;2、注册可靠交易所并完成KYC;3、启用2FA、防范信息泄露;4、通过限价单小额交易验证流程。 2025主流数字货币交易所: 1、欧易OKX 注册入口: APP下载: 2、Binance币安 注册入口: AP…

    2025年12月11日
    000
  • 数字货币被骗怎么报案_虚拟货币投资风险有哪些?

    立即收集聊天记录、交易凭证、平台截图等证据,保存原始文件并备份;通过派出所、96110或网络举报平台报案;配合警方陈述细节,提供线索,获取回执;利用区块链浏览器追踪资金流向,发现交易所入口及时协查;同时咨询律师,准备民事诉讼追偿损失。 一、立即收集并固定证据 在发现被骗后,第一时间收集和保存所有相关…

    2025年12月11日
    000
  • 什么是合约开仓均价?加仓后如何重新计算你的成本线

    开仓均价是持仓的平均成本,加仓后需重新计算。首先记录每次开仓的数量与价格,计算总成本与总持仓量,再以累计总成本除以总持仓量得出最新均价;分批建仓时应逐笔登记并统一核算;若存在多空双向头寸,须按方向分别归集计算,独立管理成本,确保盈亏基准准确。 binance币安交易所 注册入口: APP下载: 欧易…

    2025年12月11日
    000
  • 如何评估一个PFP类NFT项目的长期价值?

    1、项目团队背景需核查成员履历与行业声誉,优先选择有成熟作品和透明身份的团队。2、社区活跃度应通过Discord和Telegram互动质量及持币结构评估,避免机器人刷屏的虚假繁荣。3、艺术设计要具备视觉辨识度与稀有属性合理性,并支持IP延展。4、智能合约须开源并经审计,确认权限冻结且无隐藏风险函数。…

    2025年12月11日
    000
  • Janction (JCT)币市场定位_JCT未来价格区间预测

    JCT代币是Janction平台的核心,用于支付AI算力费用、激励节点参与和链上治理,流通量114.9亿枚,占总量22.99%。价格受交易所上线、换手率高达41%、历史波动区间0.0053-0.007美元及空投后抛压影响显著。当前估值约3.25亿美元,技术面显示0.0053美元为关键支撑,链上活跃度…

    2025年12月11日
    000
  • 如何通过Gitcoin Passport提高你的链上身份权重?

    Gitcoin Passport通过多维度验证提升身份可信度:一、连接Twitter等社交账号并完成交互以增加信任得分;二、参与Grants投票并捐赠至少5个项目,提升公民参与评分;三、绑定ENS域名与导入POAP NFT,丰富身份数据源;四、集成BrightID进行社交背书,强化防女巫攻击能力,全…

    2025年12月11日
    000
  • Phygital NFT是什么?连接物理世界与数字世界的新桥梁

    Phygital NFT通过唯一识别码将实体商品与区块链数字凭证绑定,实现物理与数字资产同步交付。制造商为产品植入NFC或二维码,并在链上铸造对应NFT,消费者扫描即可验证真伪并查看数字身份。奢侈品牌用其发售限量手袋并解锁虚拟时装秀,运动鞋品牌结合元宇宙穿戴装备,音乐会门票则集成音视频收藏品。系统依…

    2025年12月11日
    000
  • Strategy比特币溢价接近历史低位,TD Cowen仍维持买入评级

    根据最新分析,Strategy 的比特币溢价已接近历史低位,这意味着市场短期可能出现一定折价机会。然而,TD Cowen 仍然维持对比特币的 买入评级,显示机构对其长期价值保持信心。 Strategy比特币溢价分析 溢价接近历史低位通常意味着市场买入比特币的成本相对较低,同时也可能反映市场短期情绪偏…

    2025年12月11日
    000
  • 详解“灰度溢价”指标,它曾是牛市的风向标

    灰度溢价反映市场对加密资产的需求强度,其形成源于信托份额无法赎回导致套利受限,二级市场价格由供需决定;当市场需求旺盛时,交易价高于资产净值形成正溢价,常见于牛市阶段,如2021年GBTC曾超100%溢价;投资者可通过监控溢价率、设置预警线、结合链上数据判断情绪,并在高溢价时避免追涨,利用期权对冲风险…

    2025年12月11日
    000
  • 昨日贝莱德再向Coinbase转入900枚比特币,价值约7759万美元

    链上数据显示,昨日 贝莱德(BlackRock) 再次向 Coinbase 转入约 900枚比特币,按当前价格估值约为 7759万美元。此类机构入金通常预示市场流动性增加,同时可能对比特币价格短期走势产生影响。 机构入金对市场的意义 机构资金入场通常反映其对比特币长期价值的认可。此次贝莱德再度入金,…

    2025年12月11日
    000
  • 现货和合约有什么区别?为什么合约能放大收益也能归零

    现货交易是直接买卖并持有数字资产,盈亏取决于价格涨跌且无强制平仓;合约交易则是通过杠杆预测价格方向获取差价收益,引入保证金和强平机制,可双向交易但风险更高。1、现货使用稳定币按市价买入目标币种,资产实时到账并归个人所有,支持长期持有或转账。2、合约需选择永续或交割市场,设定杠杆倍数后做多或做空,仓位…

    2025年12月11日
    000

发表回复

登录后才能评论
关注微信