偶尔看到个题,一个射击运动员打靶,靶一共有10环,连开10枪打中90环的可能性有多少种?要求用递归算法解决。
递归求解是很自然的一种思路:
m枪打中n环,s(m,n)对应其射击方案
对第一枪有0-10环10种选择,假定为k环,然后对剩下的m-1枪,很显然s(m-1,n-k)对应其射击方案
因此:
很容易写一个对应的Java程序,输出射击方案
import java.util.Arrays;
public class ShootingPlan {
static int count = 0;
static int numberOfShooting = 10;
static int totalScore = 90;
public static void main(String[] args) {
//score数组表示一个射击计划,score[i]表示第i枪的环数
int [] score = new int[numberOfShooting];
Arrays.fill(score, 0);
long beginTime = System.currentTimeMillis();
shootplan(numberOfShooting, totalScore, score);
long endTime = System.currentTimeMillis();
System.out.println("Executed in " + (endTime - beginTime) + " milliseconds");
}
/**计算一个射击计划
* @param number 射击次数
* @param total 总环数
* @param score 记录成绩的数组 */
private static void shootplan(int number, int total, int[] score) {
// 边界条件,如果仅剩一枪且剩余环数符合条件,那剩余环数即为最后一枪的成绩,同时打印输出10枪成绩
if(number == 1 && total <= 10 && total >= 0) {
score[numberOfShooting - number] = total; // 输出一个射击计划
System.out.print(++count + ":");
for(int i : score)
System.out.print(i + " ");
System.out.println();
}
if(number > 1) {
for(int i = 0; i <= 10; i++) {
score[numberOfShooting - number] = i;
shootplan(number - 1, total - i, score);
}
}
}
}
运行结果最后几行如下
当然,上面这个实现谈不上什么效率,是指数级的算法:(
可以看到执行时间是18.483秒
尝试优化算法
1.考虑减小搜索空间——在递归计算中,当S(i,k)的k>10*i时,可以终止搜索,因为成绩超出上限,枪枪靶心也game over……
改进后的方法及运行结果
/**计算一个射击计划
* @param number 射击次数
* @param total 总环数
* @param score 记录成绩的数组 */
private static void shootplan(int number, int total, int[] score) {
// 边界条件,如果仅剩一枪且剩余环数符合条件,那剩余环数即为最后一枪的成绩,同时打印输出10枪成绩
if(number == 1 && total <= 10 && total >= 0) {
score[numberOfShooting - number] = total; // 输出一个射击计划
System.out.print(++count + ":");
for(int i : score)
System.out.print(i + " ");
System.out.println();
}
// 减小搜索空间
if(total > number * 10)
return;
if(number > 1) {
for(int i = 0; i <= 10; i++) {
score[numberOfShooting - number] = i;
shootplan(number - 1, total - i, score);
}
}
}
还是有明显差异的
2.是否可以借鉴动态规划算法,存储计算过的子结构以空间换时间?
感觉上是靠谱的,但这个空间开销貌似有点惊艳…… 找机会试试
下面重头戏,能不能直接计算出方案总数? (不战而屈人之兵自然爽,总不能每次都傻傻跑段代码给出结果)
重新整理下需求——10枪打90环的方案数
用组合数学里的一一对应思想转化下,考虑补集——每枪少打的环数xi'
即关注每一枪比10环少的环数,10枪打90环 等价于10枪共少打10环,那么每枪少几环的方案数和原方案数一一对应
把总共少打的10环分配到10枪中,相当于把10个球放到10个不同的盒子里,盒子可以为空
熟悉组合数学的朋友可以一眼看出,这是允许重复的组合,结果数为C(10+10-1,10) = C(19,10) = 92378
或者用隔板法考虑,把10个球放到10个不同的盒子里,盒子可以为空,等价于把10个球分成10堆
需要9个隔板,把球和隔板的位置一字排开,结果数为C(19,10)或者C(19,9) = 92378
进一步考虑,m枪打n环,如果 n>= 10m - 10,那么总的方案数为 C(10m-n+9, 9)
如果n< 10m - 10,这时候miss的环数大于10了,不能再放到一枪中了,上述公式不再适用……