方向 一|魔法甜点之和:小包的新挑战|豆包MarsCode AI刷题

164 阅读9分钟

问题描述

小R不再追求甜点中最高的喜爱值,今天他想要的是甜点喜爱值之和正好匹配他的预期值 S。为了达到这个目标,他可以使用魔法棒来改变甜点的喜爱值,使其变为原来喜爱值的阶乘。每个甜点只能使用一次魔法棒,也可以完全不用。

下午茶小哥今天带来了 N 个甜点,每个甜点都有一个固定的喜爱值。小R有 M 个魔法棒,他可以选择任意甜点使用,但每个甜点只能使用一次魔法棒。他的目标是通过选择一些甜点,可能使用魔法棒,使得这些甜点的喜爱值之和恰好为 S。

请计算小R有多少种不同的方案满足他的要求。如果两种方案中,选择的甜点不同,或者使用魔法棒的甜点不同,则视为不同的方案。


测试样例

样例1:

输入:n = 3, m = 2, s = 6, like = [1, 2, 3]
输出:5

样例2:

输入:n = 3, m = 1, s = 1, like = [1, 1, 1]
输出:6

样例3:

输入:n = 5, m = 3, s = 24, like = [1, 2, 3, 4, 5]
输出:1

样例4:

输入:n = 4, m = 0, s = 10, like = [1, 3, 3, 3]
输出:1

样例5:

输入:n = 6, m = 1, s = 35, like = [5, 5, 5, 5, 5, 5]
输出:0

题目解析

解题思路

  1. 问题理解

    • 我们需要找到一种方案,使得选择的甜点的喜爱值之和恰好为 S
    • 每个甜点可以选择不使用魔法棒、使用魔法棒(将其喜爱值变为阶乘),或者不选择该甜点。
    • 每个甜点只能使用一次魔法棒,且最多可以使用 M 次魔法棒。
  2. 数据结构选择

    • 使用 defaultdict 来存储不同喜爱值之和的方案数,以及每个方案中使用的魔法棒数量。
    • 使用递归来遍历所有可能的组合。
  3. 算法步骤

    • 预计算阶乘:为了避免重复计算,我们预先计算每个甜点的阶乘值,并存储在 factorial_cache 中。
    • 分治法:将甜点列表分为两部分,分别处理每一部分的所有可能组合。
    • 深度优先搜索 (DFS):对每一部分进行 DFS,记录所有可能的喜爱值之和及其对应的魔法棒使用次数。
    • 合并结果:将两部分的结果进行合并,计算满足条件的方案数。

代码解析;

def solution(n, m, s, like): from collections import defaultdict import sys sys.setrecursionlimit(1 << 25) # 增大递归深度限制

# 预计算阶乘,避免重复计算
factorial_cache = {}

def factorial(x):
    if x in factorial_cache:
        return factorial_cache[x]
    result = 1
    for i in range(2, x + 1):
        result *= i
        if result > s:
            result = s + 1  # 超过 s 的阶乘值设为 s+1,用于剪枝
            break
    factorial_cache[x] = result
    return result

# 将甜点分为两部分
mid = n // 2
likes_first = like[:mid]
likes_second = like[mid:]

# 处理第一部分
sum_counts_first = defaultdict(lambda: defaultdict(int))

def dfs_first(i, total_like, wands_used):
    if i == len(likes_first):
        sum_counts_first[total_like][wands_used] += 1
        return
    # 不选择当前甜点
    dfs_first(i + 1, total_like, wands_used)
    # 选择当前甜点,不使用魔法棒
    dfs_first(i + 1, total_like + likes_first[i], wands_used)
    # 选择当前甜点,使用魔法棒(如果阶乘值不超过 s)
    fact = factorial(likes_first[i])
    if fact <= s:
        dfs_first(i + 1, total_like + fact, wands_used + 1)

dfs_first(0, 0, 0)

# 处理第二部分
sum_counts_second = defaultdict(lambda: defaultdict(int))

def dfs_second(i, total_like, wands_used):
    if i == len(likes_second):
        sum_counts_second[total_like][wands_used] += 1
        return
    # 不选择当前甜点
    dfs_second(i + 1, total_like, wands_used)
    # 选择当前甜点,不使用魔法棒
    dfs_second(i + 1, total_like + likes_second[i], wands_used)
    # 选择当前甜点,使用魔法棒(如果阶乘值不超过 s)
    fact = factorial(likes_second[i])
    if fact <= s:
        dfs_second(i + 1, total_like + fact, wands_used + 1)

dfs_second(0, 0, 0)

# 合并两部分的结果
total_ways = 0
for sum_first in sum_counts_first:
    sum_second_needed = s - sum_first
    if sum_second_needed in sum_counts_second:
        counts_first = sum_counts_first[sum_first]
        counts_second = sum_counts_second[sum_second_needed]
        for wands_first in counts_first:
            for wands_second in counts_second:
                if wands_first + wands_second <= m:
                    total_ways += counts_first[wands_first] * counts_second[wands_second]

return total_ways

知识点总结

  1. 递归与深度优先搜索 (DFS)

    • 代码中使用了递归和深度优先搜索 (DFS) 来遍历所有可能的甜点组合。
    • dfs_firstdfs_second 函数分别对甜点的两部分进行 DFS,记录所有可能的喜爱值之和及其对应的魔法棒使用次数。
  2. 分治法

    • 将甜点列表分为两部分 (likes_firstlikes_second),分别处理每一部分的所有可能组合,最后合并结果。
    • 这种方法可以减少计算量,提高效率。
  3. 动态规划与记忆化搜索

    • 通过 factorial_cache 缓存阶乘值,避免了重复计算,这是一种记忆化搜索的技巧。
    • 使用 defaultdict 来存储不同喜爱值之和的方案数,以及每个方案中使用的魔法棒数量,这是一种动态规划的思想。
  4. 剪枝优化

    • 在计算阶乘时,如果阶乘值超过 s,则将其设为 s + 1,用于剪枝,避免不必要的计算。
    • 在合并结果时,只考虑魔法棒使用次数不超过 m 的组合,这也是一种剪枝优化。
  5. Python 特性

    • 使用 defaultdict 来简化字典的初始化操作。
    • 使用 sys.setrecursionlimit 来增大递归深度限制,避免递归深度过大导致的栈溢出问题。
  6. 组合数学

    • 通过组合不同甜点的选择和魔法棒的使用,计算满足条件的方案数。
    • 合并两部分的结果时,通过组合数学的方法计算总的方案数。

代码中的关键点

  1. 预计算阶乘

    factorial_cache = {}
    
    def factorial(x):
        if x in factorial_cache:
            return factorial_cache[x]
        result = 1
        for i in range(2, x + 1):
            result *= i
            if result > s:
                result = s + 1  # 超过 s 的阶乘值设为 s+1,用于剪枝
                break
        factorial_cache[x] = result
        return result
    
  2. 分治法与 DFS

    mid = n // 2
    likes_first = like[:mid]
    likes_second = like[mid:]
    
    sum_counts_first = defaultdict(lambda: defaultdict(int))
    
    def dfs_first(i, total_like, wands_used):
        if i == len(likes_first):
            sum_counts_first[total_like][wands_used] += 1
            return
        dfs_first(i + 1, total_like, wands_used)
        dfs_first(i + 1, total_like + likes_first[i], wands_used)
        fact = factorial(likes_first[i])
        if fact <= s:
            dfs_first(i + 1, total_like + fact, wands_used + 1)
    
    dfs_first(0, 0, 0)
    
  3. 合并结果

    total_ways = 0
    for sum_first in sum_counts_first:
        sum_second_needed = s - sum_first
        if sum_second_needed in sum_counts_second:
            counts_first = sum_counts_first[sum_first]
            counts_second = sum_counts_second[sum_second_needed]
            for wands_first in counts_first:
                for wands_second in counts_second:
                    if wands_first + wands_second <= m:
                        total_ways += counts_first[wands_first] * counts_second[wands_second]
    

总结

  • 递归与 DFS:通过递归和 DFS 遍历所有可能的组合。
  • 分治法:将问题分为两部分,分别处理,最后合并结果。
  • 动态规划与记忆化搜索:通过缓存和动态规划优化计算。
  • 剪枝优化:通过剪枝减少不必要的计算。
  • Python 特性:利用 Python 的特性简化代码实现。

这些知识点在解决复杂问题时非常有用,特别是在需要遍历所有可能组合的情况下。

高效学习方法

1. 制定刷题计划

目标设定

  • 短期目标:每天刷一定数量的题目,例如每天3-5题。
  • 中期目标:每周完成一个主题的题目,例如动态规划、图论等。
  • 长期目标:每月完成一个难度级别的题目,例如从简单到困难。

计划制定

  • 时间管理:每天固定时间段进行刷题,例如早上或晚上。
  • 主题划分:根据算法和数据结构的主题进行划分,每周专注于一个主题。
  • 难度递增:从简单题目开始,逐步增加难度,确保基础扎实。

工具运用

  • 豆包MarsCode AI 刷题功能:利用AI提供的题目推荐和难度评估,制定个性化的刷题计划。

2. 利用错题进行针对性学习

错题分析

  • 记录错题:将错题记录在错题本中,标注错误原因和解题思路。
  • 分类整理:根据错误类型(如逻辑错误、边界条件错误等)进行分类整理。

针对性学习

  • 重复练习:定期回顾错题,重复练习,直到完全掌握。
  • 查漏补缺:针对错误类型,查找相关知识点,进行补充学习。

工具运用

  • 豆包MarsCode AI 刷题功能:利用AI提供的错题分析和知识点推荐,进行针对性学习。

工具运用

1. 结合其他学习资源

在线课程

  • Coursera、edX:学习算法和数据结构的系统课程,如斯坦福大学的算法专项课程。
  • LeetCode、LintCode:通过在线平台进行实战练习,结合豆包MarsCode AI 刷题功能进行个性化练习。

书籍推荐

  • 《算法导论》 :系统学习算法和数据结构的基础知识。
  • 《剑指Offer》 :通过经典面试题巩固算法和数据结构的应用。

社区交流

  • GitHub:参与开源项目,学习优秀代码实现。
  • Stack Overflow:提问和解答问题,提升解决问题的能力。

2. 学习建议

坚持与反馈

  • 每日打卡:每天坚持刷题,记录学习进度和心得。
  • 定期反馈:每周或每月进行总结,分析学习效果,调整学习计划。

多样化学习

  • 视频教程:观看算法和数据结构的视频教程,加深理解。
  • 博客文章:阅读相关博客文章,了解不同解题思路和技巧。

实战演练

  • 模拟面试:定期进行模拟面试,提升实战能力。
  • 竞赛参与:参加编程竞赛,如ACM、Google Code Jam等,提升解题速度和准确性。

总结

通过制定合理的刷题计划,利用错题进行针对性学习,结合豆包MarsCode AI 刷题功能和其他学习资源,可以有效提升算法和数据结构的学习效果。坚持每日练习,定期总结反馈,多样化学习方式,将帮助你在编程学习中取得更好的成绩。