前言
"请你反转一个字符串,比如将 'abc' 变成 'cba'。"
这是一个在前端面试中出现率极高的"开胃菜"题目。很多同学看到这道题心想:"这还不简单?一行代码搞定。"
但你是否想过,面试官出一道如此简单的题,真正的目的是什么?是考察你会不会用 API?还是考察你的逻辑思维?亦或是想看你对计算机底层(如调用栈)的理解?
今天我们就通过这道看似简单的题目,拆解出 6种解法 ,并重点聊聊让许多人头疼的 递归思维。
一、 面试官在想什么?
在写代码之前,我们需要先"反向面经",揣摩面试官的考察点。对于反转字符串,通常有三个维度的考察:
- API 的熟练度:你是否熟悉 JS 的内置方法?代码是否简洁优雅?(对应工作中快速产出)
- 代码逻辑能力:脱离了 API,你能否用最基础的循环写出逻辑?
- 计算机科学素养(算法思维) :你能否用 递归 解决问题?你是否了解递归带来的栈溢出风险?
二、 青铜与白银:API 的熟练度
如果你想展示你对现代 JavaScript 的熟悉程度,以及在实际工作中解决问题的效率,以下几种方法是最"秀"的。
1. 经典 API 组合拳
最常见的解法,利用数组的 reverse 方法。
JavaScript
// 方案 1:Split + Reverse + Join
function reverseStr(str) {
// 1. 字符串 split 拆分成数组
// 2. 数组 reverse 反转
// 3. 数组 join 拼接成字符串
return str.split('').reverse().join('')
}
console.log(reverseStr('abc')) // 'cba'
2. ES6 展开运算符(Spread Syntax)
如果你想展示你对 ES6+ 新特性的掌握,可以用展开运算符代替 split,代码更具"现代感"。
JavaScript
// 方案 2:展开运算符
function reverseStr(str) {
// [...str] 等同于 str.split(''),但处理某些 unicode 字符更安全
return [...str].reverse().join('')
}
console.log(reverseStr('Hello')) // 'olleH'
三、 黄金与铂金:逻辑与循环
如果面试官追加一句:"不使用内置的 reverse 方法,你怎么做? ",这时候考察的就是你的硬编码逻辑能力了。
3. 倒序遍历(Classic For Loop)
这是最朴实无华,但也最显功底的写法。
JavaScript
// 方案 3:倒序遍历
function reverseStr(str) {
let reversed = ''
// 从最后一个字符开始,向前遍历
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i]
}
return reversed
}
console.log(reverseStr('abc')) // 'cba'
4. for...of 循环
利用正向遍历,但是通过前置拼接(reversed = x + reversed)来实现反转,这是一个巧妙的思维转换。
JavaScript
// 方案 4:for...of 前置拼接
function reverseStr(str) {
let reversed = ''
for (const x of str) {
// 注意顺序:新字符 + 旧结果
reversed = x + reversed
}
return reversed
}
console.log(reverseStr('Hello')) // 'olleH'
5. reduce 的妙用
如果你是函数式编程的爱好者,reduce 是反转字符串的神器。这也是面试官眼前一亮的写法。
JavaScript
// 方案 5:Array.prototype.reduce
function reverseStr(str) {
// cur 是当前字符,reversed 是累加器
return [...str].reduce((reversed, cur) => cur + reversed, '')
}
console.log(reverseStr('Hello')) // 'olleH'
四、 钻石段位:深入理解"递归思维" (Recursion)
这部分是本文的重点。如果面试官问:"能用递归实现吗? ",他考察的不再是 API,而是你将大问题拆解为小问题的抽象能力。
1. 什么是递归?
简单来说,递归就是函数自己调用自己 。
但在算法中,递归的核心思想是:分而治之。
2. 递归三要素
在写递归代码前,必须明确三点:
-
定义问题(函数功能) :我们要反转 str。
-
基准条件(退出条件) :什么时候停止?当字符串为空时,反转结果还是空,直接返回。
-
拆解问题(递推公式) :
- 要把 'Hello' 反转,可以看作是:反转 'ello' + 'H'。
- 要把 'ello' 反转,可以看作是:反转 'llo' + 'e'。
- 即:reverse(str) = reverse(str.substr(1)) + str.charAt(0)
3. 代码实现
JavaScript
// 方案 6:递归实现
function reverseStr(str) {
// 1. 退出条件 (基准问题)
if (str === '') {
return ''
}
// 2. 拆解问题:
// 递归调用处理"子串"(第二个字符到最后) + 拼接当前第一个字符
return reverseStr(str.substr(1)) + str.charAt(0)
}
console.log(reverseStr('Hello')) // 'olleH'
4. 拆解递归流程
让我们看看 reverseStr('Hello') 在执行时发生了什么(调用栈):
- reverseStr('Hello') 等待 reverseStr('ello') + 'H'
- reverseStr('ello') 等待 reverseStr('llo') + 'e'
- reverseStr('llo') 等待 reverseStr('lo') + 'l'
- reverseStr('lo') 等待 reverseStr('o') + 'l'
- reverseStr('o') 等待 reverseStr('') + 'o'
- reverseStr('') 命中退出条件,返回 ''
开始"归"的过程(回溯):
- 第 5 步收到 '',返回 '' + 'o' = 'o'
- 第 4 步收到 'o',返回 'o' + 'l' = 'ol'
- ...
- 第 1 步收到 'olle',返回 'olle' + 'H' = 'olleH'
5. 递归的优缺点(面试加分项)
在写完递归后,如果你能补上以下分析,面试官会觉得你很专业:
-
优点:代码语义清晰,非常符合数学定义的逻辑(Inductive Definition)。
-
缺点:
- 性能开销:每一次函数调用都会在内存栈中创建一个"栈帧"来保存上下文。
- 爆栈风险:如果字符串非常长(例如几万个字符),会导致调用栈溢出(Stack Overflow)。
五、 总结
一道简单的"反转字符串",其实是一块试金石:
- 用 split().reverse().join(''),说明你懂 API,能干活。
- 用 for 循环 / reduce,说明你逻辑扎实,懂原理。
- 用 递归 ,说明你具备算法思维,懂计算机底层结构。
下次面试遇到这道题,不妨先给出最简单的解法,然后主动告诉面试官:"其实这道题还有递归的解法,虽然有爆栈风险,但它的思维逻辑很有意思,需要我写一下吗?"
相信我,这会是你面试中的高光时刻。