我喜欢递归。我认为这是一种非常优雅的代码编写方式,有时它可以让您编写简单且有效的代码。而且,如果你碰巧使用 Scala 之类的语言,则必须使用递归。
但是,当我以为自己精通递归时,我碰到了 Federico Kereki 写的一本名为“精通 JavaScript 函数式编程”的书,在关于使用递归的部分中,他给出了如下秘诀:
- 假设你已经具备解决问题的能力。
- 然后,看看如何通过解决一个(或多个)较小的问题来解决大问题。
- 使用第 1 步中的虚构函数解决这些问题。
- 确定基本情况是什么,要尽量让它变得简单(可以直接解决),而无需任何其他调用。
当我读到这篇文章时(主要是第一点),我对这个概念感到惊讶:要递归地解决您的问题,首先假设您已经解决了问题!递归解决方案的递归解法!
尽管在我的生活中创造了相当一部分递归函数后,这种说法听起来很直观,但我觉得我必须在实践中进行测试。几天后,我解决了 LeetCode“爬楼梯”的问题。问题陈述非常简单:
您正在爬楼梯(原文如此)。它需要 n 步才能到达顶部。每次您可以爬 1 或 2 步。您可以通过几种不同的方式登顶?
因此,使用秘诀可以执行以下操作:
1)假设您已经具有解决问题的适当功能。
很简单!我假设使用此 JavaScript 代码已经具有适当的功能:
function climbStairs(n) {}
这很简单!现在,进行第二步:
2)然后,看看如何通过解决一个(或多个)较小的问题来解决大问题。
根据声明,您可以爬一两个步骤。因此,如果我假设此函数已经可以计算出我可以爬到顶部的方式的数量,那么答案将是一步之后我可以爬的方式的数量(n-1)加上两步之后我可以爬的方式的数量步骤(n-2)。
现在,第三部分:
3)使用第 1 步中的想象功能解决这些问题。
这仅意味着将该语句转换为代码,因此您将获得以下结果:
function climbStairs(n) {
return climbStairs(n - 1) + climbStairs(n - 2);
}
那就是我可以爬上一步的方法数量加上我可以爬上两步的方法数量。看起来很合理,但仍不确定其工作原理。让我们做第四步,也是最后一步:
4)确定基本情况是什么,要简单到可以直接解决,而不需要任何其他调用。
对于这个问题,我可以区分三种基本情况:
- 第一种情况是您的步数为零时,在这种情况下没有要爬的楼梯,所以爬楼梯的方式为零。
- 第二,当您的楼梯只有一步时,只有一种攀爬方法:第一步。
- 第三种,当您的楼梯有两个台阶时,有两种爬坡方法:第一种是 1 步+ 1 步;第二步分两个步骤。
function climbStairs(n) {
if (n === 0) return 0;
if (n === 1) return 1;
if (n === 2) return 2;
return climbStairs(n - 1) + climbStairs(n - 2);
}
当然,在这种情况下,可以通过将三个 if 替换为:
if (n <= 2) return n;
但是,为了使用该方法,让我们保留三个明确的条件。
就是这样!至少对于这个问题,四个配方步骤非常简单。因此,在遵循它们之后,当简单的测试用例顺利通过而没有问题时,我感到非常惊讶。
但是老实说,我最大的惊喜是我能够解决问题,但我不知道我是怎么做到的!我知道它是有效的,因为它通过了测试用例,但是我的头还没有弄清楚它是如何工作或为什么起作用的!
解决了这个问题后,我尝试了另外几个问题,并且效果总是不错,但是我仍然对此有一种敬畏的感觉。现在我不仅认为递归是一种优雅而有效的编程技术,而且还是一种魔术。
天哪,我喜欢递归!;-)