递归解决战士打靶N坏一共有多少种可能的问题

问题描述

一个战士打了10次靶,一共打了90环,问一共有多少种可能,并输出这些可能的组合。

思路

首先,嵌套10层循环进行穷举是不可取的,一是因为速度太慢,二是如果改成打20次靶就完蛋了。

其实这就是一个树的搜索问题。

1. 设第一次打了0环,那么第二次可能打0 ~ 10环这些可能

2. 以第一次打的0环为root,将第二次所有可能的环数都做为root的子结点

3. 重复1, 2步

这样就构成了一棵树,表示当第一次打了0环时所有的可能性。我们要做的就是从上到下遍历这棵树,当经过的结点之和等于90时,即命中。然后再将根结点值改成1,直到10。

那么问题来了,一棵树需要遍历多少种组合呢?设打靶次数为t, 那么所有的组合数 = 1+(11)t?1=1+(11)9 种。这个结果已经超过了4亿, 显然全部遍历一遍时间上是不能忍的。我们可以通过剪枝思想来去掉部分不必要的遍历,即判断一下即便以后全打10环时能不能满足90环的要求,如果不能则不需要继续递归了。

还有一个问题,我们真的要手动创建一个树形数据结构来执行上面的过程吗?如果这样做理论上是没问题的,但是会消耗大量的内存。 其实我们可以使用递归的方式来模拟树的遍历。

实现

定义方法

int shoot(int score, int left, int totalScores, Dequeue<Integer> path)

表示已经打了score环,还要打left枪,总环数为totalScores时所有的结果数。这里path是一个栈数据结构,用来记录递归调用的路径,从而记录了一次可能组合的各个环数。

完整代码如下:

public class Main {
    public static int SHOOT_TIMES = 10;

    public static void main(String[] args) {
        System.out.println(shoot(0, SHOOT_TIMES, 90, new LinkedList<>()));
    }

    /**
     * 返回打score环且只能打left枪且总环数为TOTAL_SCORES的所有结果数
     * @param score
     * @param left
     * @param path
     * @return
     */
    public static int shoot(int score, int left, int totalScores, Deque<Integer> path) {
        path.push(score);

        int tot = 0;
        if (1 == left) {
            // 剪枝
            // 去掉明显不可能的结果
            // 即在最后一枪时计算距离90环还剩下的环数,
            // 如果环数大于10,则不可能打满
            int left_scores = totalScores - score;
            // 当剩下的环数在0 ~ 10之间时,表明这是一个可取的组合
            if (left_scores >= 0 && left_scores <= 10) {
                path.push(left_scores);
                printStack(path);
                path.pop();

                ++tot;
            }

            path.pop();
            return tot;
        }

        for (int i = 0 ; i <= 10 ; ++i) {
            // 剪枝.
            // 计算已经打了score环时还剩下多少环.
            // 如果即便剩下全打10环还打不满90环,则表示这不是一个可取的结果
            if (totalScores - (score + i) <= 10 * left) {
                tot += shoot(score + i, left - 1, totalScores, path);
            }
        }

        path.pop();
        return tot;
    }

    /**
     * 打印出栈内的所有元素
     * @param list
     */
    private static void printStack(Deque<Integer> list) {
        int ix = 0;
        int LEN = list.size();
        for (Integer n : list) {
            if (ix == LEN - 1) {
                System.out.printf("%d\n", n);
                break;
            }
            System.out.printf("%d, ", n);

            ++ix;
        }
    }
}

如果我们把shoot()方法调用栈画出来的话,就能发现这其实就是前面描述的树,打靶的次数就是栈的深度。因此,如果我们想记录下一种组合中具体每次打的环数,就必须用一个栈来记录方法调用了。即,方法调用开始时将当前环数入栈,方法返回前出栈。 最后遍历该栈即可。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-16 13:40:35

递归解决战士打靶N坏一共有多少种可能的问题的相关文章

递归计算战士打靶S次打了N环一共有多少种可能的问题

问题描述 一个战士打了10次靶,一共打了90环,问一共有多少种可能,并输出这些可能的组合. 思路 首先,嵌套10层循环进行穷举是不可取的,一是因为速度太慢,二是如果改成打20次靶就完蛋了. 其实这就是一个树的搜索问题. 1. 设第一次打了0环,那么第二次可能打0 ~ 10环这些可能 2. 以第一次打的0环为root,将第二次所有可能的环数都做为root的子结点 3. 重复1, 2步 这样就构成了一棵树,表示当第一次打了0环时所有的可能性.我们要做的就是从上到下遍历这棵树,当经过的结点之和等于90

用1到9这九个数字变成三位数加三位数等于三位数的加法,例如:173+295 =468,一共有多少种情况?

#include "stdafx.h" #include <stdio.h> #include <vector> #include <algorithm> using namespace std; void FindCount(vector<int> &vect,int iPos,int &Count) { if (iPos>8) { int i1 = vect[0] * 100 + vect[1] * 10 + v

19:用两种颜色去染排成一个圈的6个棋子,如果通过旋转得到则只算一种,一共有多少种染色:

A: 10 B:11 C:14: D:15 答案:C 解释:应该有14种方案,设只有黑白两色,默认白色,那么,用p(n)表示有n个黑棋的种类 p(0)=p(6)=1 p(1)=p(5)=1 p(2)=p(4)=3 //相邻的一种,隔一个的一种,两个的一种 p(3)=4 //都相邻的一种,BB0B的一种,BB00B的一种,B0B0B的一种,一共4种 24:假设函数rand_k会随机返回一个[1,k]之间的随机数(k>=2),并且每个证书出现的概率相等.目前有rand_7,通过调用rand_7()和

n个台阶,每次都可以走一步,走两步,走三步,走到顶部一共有多少种可能

分析 第一个台阶  1第二个台阶  11 2    //走两次1步或者走1次两步第三个台阶  111 12 21 3 第四个台阶  1111 112 121 211 22 13 31 思想:4阶台阶,第一次可以迈1步(还剩3台阶也就是f(3)可能)或者2步(还剩2台阶也就是f(2)可能)或者3步(还剩1台阶也就是f(1)可能) f(n)=f(n-1)+f(n-2)+f(n-3)  第n个台阶的可能 = n-1台阶的可能+n-2台阶的可能+n-3台阶的可能 我这里采用了递归算法 //param x

n的阶乘结果中一共有多少个零?

题目:n的阶乘中一共有多少个零? 解答:产生零的结果只能有一种可能性那就是2*5=10,然而n的阶乘本质上是可以拆解为很多2和5以及其他不包含2和5的乘数的积,例如5的阶乘:1*2*3*4*5=1*2*3*2*2*5.按照这个思路,将n的阶乘乘积的每一项进行拆解,看看可以拆解出多少个2和多少个5,然后取2的个数和5的个数中最小的即可.程序代码如下: #include <stdio.h> int compute_zero(int n) { int five_count = 0; int two_

假设一对耗子每个月都可以生一对小耗子。小耗子生长3个月后,从第4个月开始也就能够生小耗子。问:假设所有的耗子都不死的话,那么20个月后一共有多少只耗子?

#include <stdio.h>void main(){ int i=0,old=2,first=0,second=0,third=0,sum=0; for(i=0;i<20;i++) { old=old+third; third=second; second=first; first=old; } sum=old+first+second+third; printf("20个月后一共有%d只耗子!\n",sum);} 分析图: 假设一对耗子每个月都可以生一对小耗

汉诺塔递归解决方法经典分析

一位法国数学家曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针.印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔.不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面.僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔.庙宇和众生也都将同归于尽. 虽然这只是一个传说,但也给我们提出了一个问题,

给定一个英文原文,统计文件里面一共有多少个不同的英文单词

wordsCounter.cpp // wordsCounter.cpp : Defines the entry point for the console application.// #include "stdafx.h"#include "wordsCounter.h" #ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif ////

递归解决换零钱问题--回顾总结之递归的表达能力

前面为了保持叙述的流畅,没有做太多的引申,把总结推迟到了后面. 补上一些总结,以防止出现"下面呢?下面没有了"的尴尬. 方向性问题 虽然题目在一开始就暗示了这一点,但首先,我们还是要问,它能用递归解决吗? 有点怀疑精神是好的,既要低头走路,更要抬头看路,以防止发生方向性错误,导致缘木求鱼的后果. 说这个问题能用递归解决,这种信心或者判断的依据来自于哪呢? 有人可能知道了,换零钱这个问题在<计算机程序的构造和解释>(SICP:Structure and Interpretat