为什么将数组声明为常量并分配给递归调用?
它不是。该数组是可变的。每次调用此函数都会返回一个新数组。没有数组被“覆盖”。const
在 JavaScript 中仅用于声明一个常数变量它具有词法范围(即变量arr
之后不复存在return arr
) 并且是constant(不能变异/分配)。
我得到了当前值的不变,但为什么它是在递归调用之后声明的?难道它不应该定位在之前(上面一行)以便在函数循环本身之前取消移位值吗?
您不能将其放置在上面,因为需要递归调用才能获得arr
首先。当你倒计时后n-1
to 1
,你必须前置n
在第一个位置,这就是unshift
does.
同样,为什么在 else 循环中返回完整的最终数组?难道不是应该更好地在外部返回,或者在上面的基本条件上返回,以免每次函数循环时都返回吗?
不存在“else循环”这样的东西。在那里返回数组的原因是你想要countdown(n)
返回刚刚生成的数组,使用数组countdown(n-1)
.
事实上,我的理解是,当 n 达到 0 时,循环应该返回一个空数组,而不是通过递归创建的完整数组。
正是如此if (n < 1) return [];
does.
也就是说,对于手头的任务,给出的递归实现是高度次优。首先,你不需要else
since return
无论如何都会退出该函数:
function countdown(n) {
if (n < 1) return [];
const arr = countdown(n - 1);
arr.unshift(n);
return arr;
}
其次,这是最具可读性(和性能)的简单for
loop:
function countdown(n) {
const arr = Array(n)
for (let i = 0; i < n; i++) arr[i] = n - i;
return arr;
}
(奖励:可以使用提前知道的数组大小来利用Array
构造函数以防止 JS 在推送元素时调整数组大小)。
三、目前的实施情况O(n²) 的二次时间复杂度因为arr.unshift
是应用的线性时间运算n
时间到长度数组0
to n-1
.
递归在以下情况下非常有用:需要堆栈(例如深度优先遍历)。创建倒计时不需要堆栈。这不是using反而abusing递归来实现高度次优的算法。如果您 - 无论出于何种原因(也许您被迫使用没有循环,只有递归的纯函数式语言?) - 想要替换完美的罚款for
不惜一切代价进行递归调用循环,只需使用尾递归和词法范围(闭包):
function countdown(n) {
const arr = [];
function loop(n) {
if (n < 1) return;
arr.push(n);
loop(n-1);
}
loop(n);
return arr;
}
或者如果你不喜欢闭包,那么可选的怎么样?arr
范围?这允许扭转递归,因为调用者现在拥有arr
在被调用者之前,因此可以在后续递归调用执行相同操作之前附加其编号:
function countdown(n, arr = []) {
if (n < 1) return arr;
arr.push(n);
countdown(n - 1, arr);
return arr;
}
结果相同,但(IMO)更干净,最重要的是,代码速度明显更快。尝试运行所有这些功能n = 1e6
您会发现 freeCodeCamp 未能在合理的时间内完成,而其他则几乎立即完成。