题目如下:
* 十个射击运动员打靶,靶一共有10环,10人打中90环的可能性有多少种? 请用递归算法编程实现。
关于递归,其求解过程类似一个stack,先进后出。先是由上而下调用,直至寻找到出口,然后自下而上计算返回结果。
其关键点:
1.寻找到一个“出口”,就是递归表达式在这一步可以计算出一个值。
2.定义递归表达式。注意在定义表达式n的时候,下一步n-1往往被考虑成可解的结果。(类似数学的推论证明)
一。第一步 尝试
拿到这个题目分析了一下,然后写了第一版代码:
-
- private static void Caculate(int personId,int currentScore,
- int[] scoreHistory) {
-
-
- if( 0 == personId) {
- if(90-sumup(scoreHistory)> =0 && 90-sumup(scoreHistory)<=10) {
- currentScore = 90-sumup(scoreHistory);
- scoreHistory[personId] = currentScore;
- printlog(scoreHistory);
- }
- }else {
- scoreHistory[personId] = currentScore;
-
- for(int i =0; i<=10;i++) {
- Caculate(personId-1,i,scoreHistory);
- }
- }
- }
这个递归函数的出口也找到了,表达式也找到了,应该可以求解,对吧。继续下一步,貌似可以调用函数求解咯:)。。
- public static void main(String[] args) {
- for(int i =0; i<=10;i++) {
- Caculate(9,i,store);
- }
- }
做到这里才发现,原来这个递归居然没有入口,这里是犯了第一个错误。也就是调用函数的时候,以personId=9人为起点,可是这个人的currentScore是多少呢。。。居然还需要用一个for循环去尝试,效率显然让人无语。
二。第二步 修改
这时候考虑修改入口,虽然无法猜出第一个人打几环,但是可以肯定的是,总剩余环数为90。修改递归函数如下:
-
- private static void Caculate(int personId,int remanentScore
- ,int[] scoreHistory) {
- if(0 == personId) {
- if(remanentScore>=0 && remanentScore<=10) {
- scoreHistory[personId] = remanentScore;
- printlog(scoreHistory);
- counter++;
- }
- }else {
- for(int i=0; i<=10 ; i++) {
- scoreHistory[personId] = i;
- remanentScore -= i;
- Caculate(personId-1,remanentScore,scoreHistory);
- }
- }
- }
这里就犯第二个错误了。。呵呵。18,19行的一个非常隐蔽的小bug,debug调了n久才发现!
错就错在将remanentScore进行了自减。通常在函数结尾处理一个不再使用的变量,没有什么影响的。但是这个递归函数进入到for循环,代表的是当前递归并未到出口,就是没有结束!变量的错误操作会影响到后面的计算。
解决办法就是将18,19两行改成 Caculate(personId - 1, remanentScore - i, scoreHistory); 传递正确的参数而不影响当前递归值栈
三。第三步 优化
第二步实现的递归,跟套10个for循环的效率应该是一样差。每种组合都需要跑一遍,判断。 而我们知道,如果前面2个人打了不到10环,后面8个人全中10也不可能达到90环。那么来优化一下递归函数。
-
- private static void Caculate(int personId,int remanentScore
- ,int[] scoreHistory) {
- if(remanentScore > (personId+1)*10) {
- return;
- }
-
- if(0 == personId) {
- if(remanentScore>=0 && remanentScore<=10) {
- scoreHistory[personId] = remanentScore;
- printlog(scoreHistory);
- counter++;
- }
- }else{
- for (int i = 0; i <= 10; i++) {
- scoreHistory[personId] = i;
-
-
- Caculate(personId - 1, remanentScore - i, scoreHistory);
- }
- }
- }
在函数开始就增加分数判断,不合适的直接pass 。
总共的循环次数为10的十次方10000000000 ,满足条件的次数仅92378,因此这一个简单的判断是惊天地泣鬼神的!
在我机器上运行优化过的递归时间为8688ms,而没有优化过的需要时间35766ms,显然是数量级的提高。
下面是最终JAVA代码:(改日写个Python的。。。好歹也算是混了个眼熟)
分享到:
相关推荐
### 递归:如何用三行代码找到“最终推荐人”? #### 一、递归的概念及应用场景 递归是一种非常强大的算法和技术,在计算机科学领域有着广泛的应用。它不仅仅是一种解决问题的方法,更是一种思考问题的方式。当...
11 递归:如何利用递归求解汉诺塔问题?.mp4
在数学上它以递归的方式进行定义,指这样的一个数列:0、1、1、2、3、5、8、13、21、34、55、89、144……,即前两个数为分别为0和1,从第3项开始,每项的值都等于其前两项之和。斐波那契数列Fib(n)用公式表示为: ...
Java实现矩阵中的路径(回溯法),采用非递归的回溯法,使用栈模拟递归过程。代码注释详细,可正常运行
递归是一种非常重要的编程技巧,它允许函数调用自身,直到满足一个基本情况为止。理解递归对于掌握数据结构和算法至关重要,因为很多复杂问题都可以通过递归的方式简化解决。递归通常分为两个部分:递(decreasing)...
递归基准条件:这是递归停止的条件,用于防止无限递归。 递归步骤:函数调用自身来解决更小的子问题。 注意事项 递归深度:递归深度过大可能会导致栈溢出。在编写递归程序时,应当确保基准条件能够有效地终止递归。 ...
递归先序遍历二叉树: 递归中序遍历二叉树: 递归后序遍历二叉树: 非递归先序遍历二叉树: 非递归中序遍历二叉树: 非递归后序遍历二叉树: 非递归中序遍历二叉树(算法2): 层次遍历二叉树: 递归计算单...
"爱的递归:WIP"可能是一个项目或教程,旨在通过递归技术来探讨和展示编程中的创意应用。让我们深入了解一下递归及其在JavaScript中的应用。 递归是一种算法,它通过调用自身来解决问题或执行任务。这个过程可以...
在编程领域,递归是一种强大的技术,它涉及函数或过程在执行过程中调用自身来解决问题。在Python中,递归尤为常见,因为它提供了一种简洁而优雅的方式来解决复杂的问题。本篇文章将深入探讨Python中的递归,包括其...
在编程领域,递归是一种强大的概念,它涉及函数或过程调用自身来解决问题。在C语言中,递归被广泛用于解决复杂问题,因为它能够简化代码结构并使其更易于理解。本文将深入探讨C语言中的各种递归类型,并提供一些递归...
非递归算法解决了这个问题,提供了一个不同于传统递归解决方案的方法。对于n个圆盘的情况,非递归算法的核心在于通过交替执行两个步骤来移动圆盘: 1. **步骤1**:根据圆盘数量的奇偶性,按顺时针方向将圆盘1从当前...
递归是一种在编程中解决问题的重要方法,特别是在Python这样的高级语言中。它涉及到函数或过程调用自身,通常用于处理分治、树形结构、回溯等复杂问题。递归的核心概念在于它解决问题的方式是自相似的,即问题的解决...
面试题 1:二维数组中的查找(考点: 数组) 1 面试题 2:替换空格(考点: 字符串) 2 面试题 3:从尾到头打印链表(考点: 链表) 2 面试题 4:重建二叉树(考点: 树) 4 ...面试题 10:矩形覆盖(考点: 递归和循环) 8
### 递归实现:汉诺塔问题 #### 经典问题背景 汉诺塔(Hanoi Tower)问题是一个经典的递归问题,源自一个古老的故事。传说在印度的一个神殿里,有三根金刚石柱子,第一根柱子上自上而下按大小顺序叠着64个金盘。...
函数的递归调用与分治策略
在本主题中,我们将深入探讨二叉树的三种主要遍历方法:中序遍历、前序遍历和后序遍历,以及如何通过递归和非递归的方式实现这些遍历。 首先,让我们理解递归遍历的概念。递归是一种解决问题的方法,它将问题分解为...
05_JavaSE面试题:递归与迭代
扫雷递归:Nur Besser和Natürlicher的Web矿山机器人
遍历递归的先中後序, 非递归的先中後序, 计算出深度 结点数 /* 运行结果: ------------------------ 请先序输入二叉树(如:ab三个空格表示a为根节点,b为左子树的二叉树) ab c 先序递归遍历二叉树: a b c 先序...
递归是一种重要的编程思想,它的本质是将大问题分解为小问题,然后通过小问题的解决方法来解决大问题。在理解递归之前,我们首先要明白递归程序的结构。一个递归程序通常包含三个主要部分:递归的出口、分解子问题的...