问题描述
小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
题目解析
解题思路
-
问题理解:
- 我们需要找到一种方案,使得选择的甜点的喜爱值之和恰好为
S。 - 每个甜点可以选择不使用魔法棒、使用魔法棒(将其喜爱值变为阶乘),或者不选择该甜点。
- 每个甜点只能使用一次魔法棒,且最多可以使用
M次魔法棒。
- 我们需要找到一种方案,使得选择的甜点的喜爱值之和恰好为
-
数据结构选择:
- 使用
defaultdict来存储不同喜爱值之和的方案数,以及每个方案中使用的魔法棒数量。 - 使用递归来遍历所有可能的组合。
- 使用
-
算法步骤:
- 预计算阶乘:为了避免重复计算,我们预先计算每个甜点的阶乘值,并存储在
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
知识点总结
-
递归与深度优先搜索 (DFS):
- 代码中使用了递归和深度优先搜索 (DFS) 来遍历所有可能的甜点组合。
dfs_first和dfs_second函数分别对甜点的两部分进行 DFS,记录所有可能的喜爱值之和及其对应的魔法棒使用次数。
-
分治法:
- 将甜点列表分为两部分 (
likes_first和likes_second),分别处理每一部分的所有可能组合,最后合并结果。 - 这种方法可以减少计算量,提高效率。
- 将甜点列表分为两部分 (
-
动态规划与记忆化搜索:
- 通过
factorial_cache缓存阶乘值,避免了重复计算,这是一种记忆化搜索的技巧。 - 使用
defaultdict来存储不同喜爱值之和的方案数,以及每个方案中使用的魔法棒数量,这是一种动态规划的思想。
- 通过
-
剪枝优化:
- 在计算阶乘时,如果阶乘值超过
s,则将其设为s + 1,用于剪枝,避免不必要的计算。 - 在合并结果时,只考虑魔法棒使用次数不超过
m的组合,这也是一种剪枝优化。
- 在计算阶乘时,如果阶乘值超过
-
Python 特性:
- 使用
defaultdict来简化字典的初始化操作。 - 使用
sys.setrecursionlimit来增大递归深度限制,避免递归深度过大导致的栈溢出问题。
- 使用
-
组合数学:
- 通过组合不同甜点的选择和魔法棒的使用,计算满足条件的方案数。
- 合并两部分的结果时,通过组合数学的方法计算总的方案数。
代码中的关键点
-
预计算阶乘:
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 -
分治法与 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) -
合并结果:
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 刷题功能和其他学习资源,可以有效提升算法和数据结构的学习效果。坚持每日练习,定期总结反馈,多样化学习方式,将帮助你在编程学习中取得更好的成绩。