最近刷到一个帖子,一个学计算机的朋友问:“有什么不用写代码还能干一辈子的工作,别开玩笑兄弟们?”这问题问得挺扎心的,毕竟学计算机的,不写代码还真有点对不起这个专业。但网友们的回复也很有意思,咱一起来看看。
有人说:“大学老师,写一份教案用到退休。”这确实不用天天敲代码,最多敲敲讲义,日子倒也挺惬意的。
还有网友提到:“网吧网管,会修电脑就行,不用写代码,能干一辈子,就是工资不高。”干一辈子是可以的,但有多少人愿意呢?
当然,也有直接泼冷水的:“你先开的玩笑。”哈哈,兄弟这是真实之言,学计算机的,不写代码,总觉得差点意思。
也有网友认真建议:“考公考编。”虽然不用写代码,但这条路上竞争也不小。
所以说,学计算机如果不想写代码,选工作还是得看自己的兴趣和追求。你愿意平平稳稳,还是想跳出舒适圈?反正我觉得,选啥都别亏待了自己。
今日算法题
好了,要想在IT混的好,还得看你的技术怎么样,我们言归正传,今天咱们聊聊尾递归这个话题。要是面试官问你“举例说明尾递归的理解及其应用场景”,你会怎么回答?别急,咱们一起来解剖一下这个经典的面试问题。
尾递归是递归的一种特殊形式,说白了,就是在一个函数的最后一步调用自身,而且没有其他额外操作。这样做的好处是啥呢?
简单来说,它能优化内存使用,避免普通递归可能遇到的“栈溢出”问题。啥是栈溢出?想象一下,你的电脑递归调用太多了,系统撑不住,就崩了。这不就跟吃太多饭肚子爆炸一个道理么?
举个简单的例子,阶乘大家都熟悉吧?如果用普通递归来实现计算 ( n! ) (也就是从1乘到n),代码是这样的:
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
这个函数运行时,调用栈就像一层层的积木,每次递归都得把“n * factorial(n - 1)”压进栈里。最后计算完成时再一层层拆积木,把结果拿出来。如果数字特别大,比如factorial(10000)
,你想象一下,积木能堆多高?
换成尾递归呢?代码这么写:
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
console.log(factorial(5)); // 120
这里妙就妙在“尾递归”的设计:每次调用直接把当前的结果传递给下一层,不用积木似的堆起来。就好比一个勤劳的小蜜蜂,每次只记住当前的进度,不用扛着整个任务走。结果就是啥?即使 factorial(10000)
也不会栈溢出。是不是听起来有点神奇?
那么尾递归的应用场景有哪些呢?其实很多地方都能用上。下面几个例子供大家参考:
1、数组求和
普通递归写法是这样的:
function sumArray(arr) {
if (arr.length === 1) return arr[0];
return arr.pop() + sumArray(arr);
}
console.log(sumArray([1, 2, 3, 4])); // 10
这个方法看似优雅,但当数组特别大时会崩溃,因为调用栈太深。换成尾递归就好多了:
function sumArray(arr, total = 0) {
if (arr.length === 0) return total;
return sumArray(arr.slice(1), total + arr[0]);
}
console.log(sumArray([1, 2, 3, 4])); // 10
尾递归版本每次调用都干净利落,把当前的累加值传给下一层,稳如老狗。
2、斐波那契数列
斐波那契数列那玩意儿,前两项是1,后面每一项等于前两项之和。普通递归实现是这样的:
function fibonacci(n) {
if (n <= 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(10)); // 55
这个写法性能堪忧,因为每次都要计算两次,效率感人。尾递归版本:
function fibonacci(n, a = 1, b = 1) {
if (n <= 2) return b;
return fibonacci(n - 1, b, a + b);
}
console.log(fibonacci(10)); // 55
看看尾递归的魅力,每次只传递当前两个数的值,计算速度嗖嗖的。
3、数组扁平化
要把一个嵌套数组变成一维数组,普通写法用递归:
function flatten(arr) {
let result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result.push(item);
}
});
return result;
}
console.log(flatten([1, [2, [3, [4]]]])); // [1, 2, 3, 4]
尾递归优化版:
function flatten(arr, result = []) {
arr.forEach(item => {
if (Array.isArray(item)) {
flatten(item, result);
} else {
result.push(item);
}
});
return result;
}
console.log(flatten([1, [2, [3, [4]]]])); // [1, 2, 3, 4]
尾递归版本只用一个数组保存结果,不用反复创建中间结果数组,内存占用更省。
4、对象格式化
有时我们想把对象的所有键变成小写,比如:
let obj = {
A: '1',
B: {
C: '2',
D: {
E: '3'
}
}
};
转换后变成:
let obj = {
a: '1',
b: {
c: '2',
d: {
e: '3'
}
}
};
尾递归实现:
function keysToLowerCase(obj) {
let result = {};
for (let key in obj) {
let newKey = key.toLowerCase();
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
result[newKey] = keysToLowerCase(obj[key]);
} else {
result[newKey] = obj[key];
}
}
return result;
}
console.log(keysToLowerCase(obj));
尾递归方式逐层遍历,既简洁又高效。
最后给大家总结一下如何回答面试题:
定义清楚:尾递归是递归的一种特殊形式,特点是递归调用发生在函数的最后一步。 优缺点简述:尾递归通过优化栈使用,避免栈溢出;但只有在支持尾调用优化的环境下(如部分JS引擎)才能完全发挥作用。 举例说明:阶乘、斐波那契数列、数组求和、扁平化操作和对象格式化等。 强调实践:说明尾递归的实际意义在于优化性能和内存,尤其在处理大规模数据或深度嵌套时。
目前,对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥私藏精品 热门推荐 虎哥作为一名老码农,整理了全网最全《前端资料合集》。