某组织举行会议,来了多个代表团同时到达,接待处只有一辆汽车,可以同时接待多个代表团,为了提高车辆利用率,请帮接待员计算可以坐满车的接待方案,输出方案数量。
约束:
一个团只能上一辆车,并且代表团人数 (代表团数量小于30,每个代表团人数小于30)小于汽车容量(汽车容量小于100) 需要将车辆坐满
第二行 汽车载客量,汽车容量小于100
5,4,2,3,2,4,9
10
4
说明 解释 以下几种方式都可以坐满车,所以,优先接待输出为4
[2,3,5]
[2,4,4]
[2,3,5]
[2,4,4]
这个问题通过 动态规划(DP)来解决。我们利用递归来枚举所有的选择,使用记忆化递归来避免重复计算,提高效率。
解题步骤
递归问题建模:
我们要递归地选择每个数,要么包括它(从 nums[i] 中选一个),要么不包括它。每次选择后,问题的规模会减少。
如果当前已经找到了满足条件的组合(即剩余的目标和 remaining 为 0),则返回 1。
如果剩余目标 remaining 小于 0 或者已经遍历完所有元素,则返回 0。
记忆化递归:
为了避免重复计算,我们使用 lru_cache 对递归结果进行缓存,这样相同的子问题只会被计算一次。
递归函数 dp(i, remaining) 的定义:
i 是当前处理的数组索引。
remaining 是当前剩余需要求和的目标值。
状态转移:
对于每个索引 i,我们有两种选择:
包括 nums[i],目标值变为 remaining - nums[i],继续递归。
不包括 nums[i],继续递归。
最终的返回值是这两者的和。
输入输出:
输入 nums 是一个整数列表,bag 是目标和。
输出是符合条件的组合数量。
from functools import lru_cache # 引入lru_cache用于缓存递归结果,优化性能
def find_combinations(nums, target):
# 使用lru_cache装饰器,缓存递归结果,None表示不限制缓存的数量
@lru_cache(None)
def dp(i, remaining):
# 如果剩余目标为0,说明当前组合已经满足条件,返回1
if remaining == 0:
return 1
# 如果剩余目标小于0或者已经遍历完所有元素,返回0
if remaining < 0 or i == len(nums):
return 0
# 递归两种选择:选择当前元素nums[i],或者不选择当前元素
include = dp(i + 1, remaining - nums[i]) # 包括当前元素
exclude = dp(i + 1, remaining) # 不包括当前元素
# 返回包括和不包括当前元素的总和
return include + exclude
# 调用dp函数,从第一个元素开始,目标和为target
return dp(0, target)
# 读取输入
nums = list(map(int, input().split(","))) # 输入nums列表
bag = int(input()) # 输入目标和bag
# 输出符合条件的组合数量
print(find_combinations(tuple(nums), bag)) # 将nums转换为元组传递给dp函数,因为lru_cache要求不可变类型
动态规划解法(记忆化递归)
递归建模:
我们要递归地计算:从数组的第 i 个元素开始,目标和是 remaining 时,符合条件的组合数量。
每个数字有两种选择:要么包括当前数字,要么不包括当前数字。
如果剩余目标和 remaining == 0,则返回 1,表示找到了一种组合。
如果 remaining < 0 或者遍历完所有数字,则返回 0,表示当前路径无解。
记忆化递归:
由于存在重复子问题,使用 HashMap 来缓存计算过的结果,避免重复计算。使用 (i, remaining) 作为缓存的键,i 表示当前处理的数字索引,remaining 表示剩余的目标和。
递归过程:
对于每个索引 i,我们有两种选择:
包括 nums[i],递归处理剩余目标 remaining - nums[i]。
不包括 nums[i],递归处理目标和 remaining。
最终返回两者的和,表示包括和不包括当前数字的所有组合。
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入的整数数组
String[] input = scanner.nextLine().split(",");
int[] nums = new int[input.length];
for (int i = 0; i < input.length; i++) {
nums[i] = Integer.parseInt(input[i].trim()); // 将输入的字符串数组转换为整数数组
}
// 读取目标和
int target = Integer.parseInt(scanner.nextLine());
// 创建一个 Main 实例并调用 findCombinations 方法
Main solution = new Main();
int result = solution.findCombinations(nums, target);
// 输出结果
System.out.println(result);
}
// 存储中间结果的哈希表,用于记忆化递归
private Map<String, Integer> memo = new HashMap<>();
// 主方法:调用递归方法 dp 来计算组合数量
public int findCombinations(int[] nums, int target) {
return dp(nums, 0, target); // 从数组的第一个元素开始,目标和为 target
}
// 递归方法:dp(i, remaining) 表示从索引 i 开始,目标和剩余为 remaining 时的组合数量
private int dp(int[] nums, int i, int remaining) {
// 如果剩余目标和为0,说明找到了一个符合要求的组合,返回1
if (remaining == 0) {
return 1;
}
// 如果剩余目标和小于0,或者遍历到数组的末尾,则说明无解,返回0
if (remaining < 0 || i == nums.length) {
return 0;
}
// 创建一个缓存键,键值为当前索引和剩余目标和
String key = i + "," + remaining;
// 如果当前状态已经计算过,直接返回缓存结果
if (memo.containsKey(key)) {
return memo.get(key);
}
// 递归两种情况:包括当前数字 nums[i],或者不包括当前数字
int include = dp(nums, i + 1, remaining - nums[i]); // 包括当前数字
int exclude = dp(nums, i + 1, remaining); // 不包括当前数字
// 将当前结果存入缓存
int result = include + exclude;
memo.put(key, result);
// 返回当前状态的结果
return result;
}
}
更新中
更新中
更新中
如果发现代码有用例覆盖不到的情况,欢迎反馈!会在第一时间修正,更新。
解题不易,如对您有帮助,欢迎点赞/收藏