算法笔记练习 4.3 递归 问题 C: 神奇的口袋
题目
题目描述
有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。
输入
输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。
输出
输出不同的选择物品的方式的数目。
样例输入
2
12
28
3
21
10
5
样例输出
1
0
思路
递归法
假定有 n 件物品,他们的重量存放于数组weight
中,目标重量为target
。
要解决的问题:n 个物品正好凑成target
,有几种方案?
拆分成子问题:
- 用重量为
weight[0]
的第 1 个物品,剩下的物品正好凑成target-weight[0]
,有几种方案? - 不用第 1 个物品,剩下的物品正好凑成
target
,有几种方案?
首先,递归调用应该是这样的:在没遇到递归边界(即target > 0
,还需要更多物品)的时候,方案数等于上述两个子问题的方案数相加。
然后来确定递归边界:
- 当
target == 0
的时候,说明出现了一种可行的方案; - 若 1 不成立,那么当
target < 0
或者n == 0
(没有物品可用)的时候,不可能再出现可行方案。
注意 1 和 2 的优先级,如果先判断 2 再判断 1 的话,正好用上最后一个物品凑成target
的情况会被误判。
代码
递归法
#include <stdio.h>
#define MAXN 20
#define VOLUME 40
/* countPlans
参数: 目标重量 target
表示 n 个物品重量的数组 weights
返回用数组中的物品恰好配成目标重量的方案数量 */
int countPlans(int target, int n, int *weights);
int main(){
int n, i;
while (scanf("%d", &n) != EOF){
int weights[n];
for (i = 0; i < n; ++i)
scanf("%d", &weights[i]);
printf("%d\n", countPlans(VOLUME, n, weights));
}
return 0;
}
int countPlans(int target, int n, int *weights){
if (target == 0) return 1;
else if (n == 0 || target < 0) return 0;
else return countPlans(target-weights[0], n-1, weights+1)
+ countPlans(target, n-1, weights+1);
}
第一次 AC 代码
第一次做这道题,递归写得很罗嗦。
原因是边界条件没考虑好,写的过分“安全”了,然后为了匹配边界条件,把边界调用分成三种情况来讨论。
也记录一下代码,思路见注释。
#include <stdio.h>
#define MAXN 20
#define VOLUME 40
/* countPlans
参数: 目标重量 target
表示 n 个物品重量的数组 weights
返回用数组中的物品恰好配成目标重量的方案数量 */
int countPlans(int target, int n, int *weights);
int main(){
int n, i;
while (scanf("%d", &n) != EOF){
int weights[n];
for (i = 0; i < n; ++i)
scanf("%d", &weights[i]);
printf("%d\n", countPlans(VOLUME, n, weights));
}
return 0;
}
int countPlans(int target, int n, int *weights){
// 递归边界:还剩 0 个物品或者还需要 0 单位体积
// 此时不可能会再有新方案了,返回 0
if (n == 0 || target == 0){
return 0;
// 若第一个物品的重量等于剩余目标,有两种选择
// 1. 直接将其纳入,补足剩余重量,这种选择的方案数量为 1
// 2. 不用该物品,这种选择的方案数量需要递归调用来得到
} else if (weights[0] == target){
return 1 + countPlans(target, n-1, weights+1);
// 若第一个物品的重量小于剩余目标
// 同样有两种选择,用或者不用第一个物品,都需要递归调用
} else if (weights[0] < target){
return countPlans(target, n-1, weights+1)
+ countPlans(target-weights[0], n-1, weights+1);
// 若第一个物品的重量大于剩余目标,只能跳过
} else return countPlans(target, n-1, weights+1);
}