递归是当一个函数调用自己并且有一些(非正式的)规则在编写这些规则时总是很好地记住:
1。基本情况。
每个递归函数都必须有一个基本情况,它基本上充当recursive call 中堆栈的末尾。
2。每个递归函数都遵守non-base(s) 和base case。
换句话说,您的代码必须以函数调用自身或终止递归调用的方式编写。您可以通过执行if 和 else 语句来做到这一点,或者只编写if 语句来捕获基本情况。
3。函数的输入要记住前一个函数的状态。
在数学中,您可能还记得调用自己的函数(为了解释而转换了语法):
f(x)_(n=0) = f(x)_(n=1) + 10
变成:
f(x)_(n=1) = ( f(x)_(n=2) + 10 ) + 10
等等。本质上,您正在使用代码编写此代码并设置一个基本情况,可能会说(对于上面的示例,即)“当n 为 10 时停止”。如果是这种情况,您应该注意到当我们深入到该函数以及f(x)_(n=10) 出现时的级联效应(并假设返回0 + 10)我们将如何获得f(x)_(n=0) = 0 + 10 + 10 + 10 + ... 的最终形式。
所以对于这个函数,你有两个输入,nums 和 x。这些输入是我们将在递归堆栈中进行修改的内容。
1。编写我们的基本案例。
编写基本情况通常是编写递归函数最简单的部分。我们知道,对于您的问题,必须抓住以下情况:
- 如果
x不在nums的长度范围内,那么我们必须返回0。
- 如果
len(nums) 是0,那么我们应该返回0。
让我们开始吧:
def sum_elements(nums, x) -> int:
if len(nums) == 0 or not x in range(-len(nums), len(nums)):
return 0
但请注意,range(len([1, 2])) 将返回 range(0, 2),但 list(range(0, 2)) 将返回 [0, 1]。因此,我们必须确保在我们的len(nums) 中添加一个1,这样我们才能真正看到x 是否在适当的范围内:
def sum_elements(nums, x) -> int:
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
请注意,range(-len(nums), len(nums) + 1) 当nums = [1, 2, 3] 等于range(-3, 4),但list(range(-3, 4)) 等于[-3, -2, -1, 0, 1, 2, 3]。因此,我们不需要-len(nums) + 1 或-len(nums) - 1。
一旦我们弄清楚了基本情况,我们就可以开始处理我们的实际功能了。至此,我们已经完成了 #1 和 #2 的一部分,但现在我们必须编写 non-base(s) 案例。
2。识别我们的other-case(s):
正如 #2 中所写,我们的函数输入是随着函数堆栈向下移动而动态变化的。因此,我们需要考虑如何修改nums 和/或x 以适应我们的目的。但是,您应该首先查看的是,如果我们在向下堆栈时仅更改其中一个变量会发生什么。
- 保持
nums 不变,修改x:我们知道我们的基本情况确保x 在正方向和负方向上都保持在nums 的长度约束内,这很好。但是,每次函数由原始 x 或x_0 运行时,我们都必须增加x。如果我们创建函数并在每次调用时都说x + x,我们不会将原始x 添加到自身,而是将更新的x 添加到自身。这是个问题。举个例子:
def sum_elements(nums, x) -> int:
print(nums, x)
# Base case.
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
# Other case. We must differentiate between positive x, and negative x.
if x > 0:
# Since x is an index that starts at 1, not 0, we must do x-1.
number = nums[x - 1]
else:
# For negative values of x this does not apply. [1, 2][-2] = 1
number = nums[x]
return number + sum_elements(nums, x + x)
注意我们是如何得到的:
# [NUMS] x
[1, 2, 3, 4, 5, 6] 2
[1, 2, 3, 4, 5, 6] 4
[1, 2, 3, 4, 5, 6] 8
# OUTPUT
6
以及第三次调用中的x 值是8。这不是bueno。你练习递归的次数越多,这个概念就会变得越直观,因为你会注意到改变某个输入可能不是最好的。你应该想:“当函数继续向下堆栈时,这个值会是什么?”
- 保持
x 不变,修改nums:如果我们这样做,我们应该确定x 的值不会有问题。那么问题就变成了我们将如何修改nums 列表并使用x 来获得优势。我们所知道的是,x 在技术上可以用作索引,如上所示。因此,如果我们不修改索引,而是修改该索引所在的列表,该怎么办?举个例子:
nums = [1, 2, 3, 4]
x = 2
print(nums) # > [1, 2, 3, 4]
print(nums[x - 1]) # > 2
nums = nums[x:] # > [3, 4]
print(nums[x - 1]) # > 4
所以看起来我们可以修改列表并保持一个常量x 来检索我们想要的信息。惊人的!在这种情况下,#2是要走的路。
3。写我们的other-case(s)。
所以现在我们将尝试编写一个保持x 不变但修改nums 的函数。从上面的代码中我们有了一个大致的思路,从前面的点我们知道我们将不得不以不同的方式处理-x 和x。因此,让我们写点东西:
def sum_elements2(nums, x) -> int:
# Base case.
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
# Other case.
if x >= 0:
number = nums[x - 1]
nums = nums[x:]
else:
number = nums[x]
# Not sure what goes here.
return number + sum_elements(nums, x)
如果我们测试上面的函数,它似乎适用于任何正值x,但不适用于负值x。然而,这是有道理的,无论我们对积极的一面做什么,我们都必须对消极的一面做相反的事情。如果我们尝试使用nums = nums[:x],我们很快就会意识到它有效。我们的最终函数变为:
def sum_elements(nums, x) -> int:
# Base case.
if len(nums) == 0 or not x in range(-len(nums), len(nums) + 1):
return 0
# Other case.
if x >= 0:
number = nums[x - 1]
nums = nums[x:]
else:
number = nums[x]
nums = nums[:x]
return number + sum_elements(nums, x)
运行示例
如果我们使用上述函数运行示例,我们会得到:
print(sum_elements([1, 2, 3, 4, 5, 6], 2)) # > 2 + 4 + 6 = 12
print(sum_elements([], 0)) # > 0
print(sum_elements([1, 5, 2, 5, 9, 5], 3)) # > 7
print(sum_elements([5, 6, 10, 20], -2)) # > 15
print(sum_elements([5, 6, 10, 20], -20)) # > 0