导读:本期聚焦于小伙伴创作的《如何正确实现带记忆化的组合求和递归函数how_sum》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何正确实现带记忆化的组合求和递归函数how_sum》有用,将其分享出去将是对创作者最好的鼓励。

组合求和问题中,how_sum函数的核心需求是:给定一个正整数目标和target_sum,以及一个正整数数组numbers,判断能否从数组中选取若干元素(可重复选取),使得它们的和等于target_sum,如果能则返回其中一种组合方案,不能则返回None。普通的递归实现会存在大量重复子问题的计算,时间复杂度会随着目标值的增大呈指数级上升,而记忆化技术可以通过缓存已经计算过的子问题结果,大幅降低计算开销。

如何正确实现带记忆化的组合求和递归函数how_sum

普通递归实现的缺陷

我们先来看没有记忆化的普通递归实现,递归的核心逻辑是:如果当前目标值等于0,说明找到了组合,返回空数组;如果当前目标值小于0,说明当前路径不可行,返回None;否则遍历数组中的每个元素,用当前目标值减去该元素得到新的目标值,递归调用函数,如果递归返回结果不为None,就把当前元素加入结果数组返回。

def how_sum(target_sum, numbers):
    # 基线条件:目标和为0,返回空数组表示找到组合
    if target_sum == 0:
        return []
    # 目标和为负,当前路径不可行
    if target_sum < 0:
        return None
    # 遍历数组中的每个元素
    for num in numbers:
        remainder = target_sum - num
        result = how_sum(remainder, numbers)
        # 如果子问题有解,把当前元素加入结果
        if result is not None:
            return result + [num]
    # 所有路径都不可行,返回None
    return None

这种实现的问题在于,同一个remainder值会被反复计算多次。比如目标值是7,数组是[2,3],计算how_sum(7,[2,3])的时候,会先算how_sum(5,[2,3]),再算how_sum(4,[2,3]),而how_sum(5,[2,3])内部又会计算how_sum(3,[2,3])、how_sum(2,[2,3]),后续其他分支也会重复计算这些子问题,当目标值较大时,计算量会急剧增加。

添加记忆化的实现方式

记忆化的核心思路是用一个字典(或者数组)缓存已经计算过的target_sum对应的结果,每次递归前先检查缓存中是否有该target_sum的结果,如果有直接返回,不需要重复计算;如果缓存中没有,先计算得到结果,再把结果存入缓存后返回。

步骤1:定义缓存结构

我们可以使用一个字典作为缓存,键是target_sum的值,值是该target_sum对应的how_sum结果,初始化时缓存为空。

步骤2:修改递归逻辑加入缓存判断

在递归函数的开头,先判断当前target_sum是否在缓存中,如果在直接返回缓存值;递归计算得到结果后,先把结果存入缓存,再返回结果。

完整的带记忆化的how_sum函数实现如下:

def how_sum(target_sum, numbers, memo={}):
    # 先检查缓存中是否有当前目标值的结果
    if target_sum in memo:
        return memo[target_sum]
    # 基线条件:目标和为0,返回空数组
    if target_sum == 0:
        return []
    # 目标和为负,当前路径不可行
    if target_sum < 0:
        return None
    # 遍历数组中的每个元素
    for num in numbers:
        remainder = target_sum - num
        result = how_sum(remainder, numbers, memo)
        # 如果子问题有解,把当前元素加入结果
        if result is not None:
            memo[target_sum] = result + [num]
            return memo[target_sum]
    # 所有路径都不可行,缓存None结果并返回
    memo[target_sum] = None
    return None

两种实现的性能对比

我们可以通过测试不同目标值的运行时间,来直观看到记忆化的优化效果。测试代码如下:

import time

# 普通递归版本
def how_sum_normal(target_sum, numbers):
    if target_sum == 0:
        return []
    if target_sum < 0:
        return None
    for num in numbers:
        remainder = target_sum - num
        result = how_sum_normal(remainder, numbers)
        if result is not None:
            return result + [num]
    return None

# 带记忆化版本
def how_sum_memo(target_sum, numbers, memo={}):
    if target_sum in memo:
        return memo[target_sum]
    if target_sum == 0:
        return []
    if target_sum < 0:
        return None
    for num in numbers:
        remainder = target_sum - num
        result = how_sum_memo(remainder, numbers, memo)
        if result is not None:
            memo[target_sum] = result + [num]
            return memo[target_sum]
    memo[target_sum] = None
    return None

# 测试数据
target = 300
nums = [7, 14]

# 测试普通递归
start = time.time()
print(how_sum_normal(target, nums))
end = time.time()
print(f"普通递归耗时:{end - start:.4f}秒")

# 测试记忆化递归
start = time.time()
print(how_sum_memo(target, nums))
end = time.time()
print(f"记忆化递归耗时:{end - start:.4f}秒")

运行上述代码可以看到,普通递归计算target为300、数组为[7,14]的场景时,会运行很长时间甚至无法得到结果,因为7和14都是7的倍数,永远无法组合出300,普通递归会遍历所有可能的组合路径,计算量极大;而记忆化版本几乎瞬间就能返回None,因为所有计算过的remainder都会被缓存,每个子问题只会计算一次。

实现时的注意事项

  • 缓存的键必须是不可变类型,这里target_sum是整数,符合字典键的要求,如果子问题的参数是数组,需要先把数组转为元组再作为缓存键。
  • 当递归返回None时,也需要把None存入缓存,避免后续重复计算同一个不可行的target_sum。
  • 如果是在类方法中实现记忆化,不要使用默认参数为空字典,因为Python的默认参数是函数定义时创建的,会被所有调用共享,可能导致缓存混乱,建议在函数内部初始化缓存。

通过上述实现,我们就可以正确完成带记忆化的how_sum组合求和递归函数,既保证了结果的正确性,又大幅提升了函数的执行效率,这种记忆化的思路也可以应用到其他类似的递归问题中。

how_sum记忆化递归组合求和动态规划修改时间:2026-07-09 04:06:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。