🔁 字符串反转 × 两数之和:前端面试高频题深度拆解(附5种反转写法 + 哈希优化)

在前端面试中, "反转字符串""两数之和" 虽看似简单,却是考察候选人 基础扎实度、代码思维、API 熟练度与算法意识 的经典组合拳。本文将带你:

  • 5 种方式实现字符串反转,对比优劣;
  • 深入剖析 两数之和的暴力解 vs 哈希优化
  • 揭秘面试官真正想考察什么;
  • 提供可直接复用的高质量代码模板。

🔄 一、字符串反转:不止一种写法

场景

输入 'hello',输出 'olleh'

💡 面试官关注点:

  • 是否熟悉数组/字符串 API(split, reverse, join
  • 能否写出清晰、健壮的循环逻辑
  • 是否理解递归思想及其风险
  • 是否会用现代语法(如扩展运算符、reduce)

✅ 方法1:经典三连(API 流)

perl 复制代码
js
编辑
function reverseStr(str) {
  return str.split('').reverse().join('');
}

const str = "hello"; 
const arr = str.split(''); 
// arr 的值会是:['h', 'e', 'l', 'l', 'o']

const arr = ['h', 'e', 'l', 'l', 'o'];
arr.reverse(); 
// 调用后,arr 本身被修改了,现在的值是:['o', 'l', 'l', 'e', 'h']

const reversedArr = ['o', 'l', 'l', 'e', 'h']; 
const reversedStr = reversedArr.join(''); 
// reversedStr 的值会是:"olleh"
  • 优点:简洁、可读性强,体现对内置方法的掌握。
  • 缺点:创建中间数组,内存开销略高(但通常可忽略)。

✅ 方法2:传统 for 循环(从后往前)

ini 复制代码
js
编辑
function reverseStr(str) {
  let reversed = '';
  for (let i = str.length - 1; i >= 0; i--) {
    reversed += str[i];
  }
  return reversed;
}
  • 优点:逻辑直观,兼容性好(ES3 起支持)。
  • 注意:字符串拼接在旧引擎中可能低效(现代 V8 已优化)。

✅ 方法3:for...of 反向拼接

csharp 复制代码
js
编辑
function reverseStr(str) {
  let reversed = '';
  for (const char of str) {
    reversed = char + reversed; // 每次把新字符放前面
  }
  return reversed;
}
  • 优点:避免索引操作,更符合"遍历字符"语义。
  • 缺点:频繁字符串拼接(虽现代 JS 引擎已优化)。

✅ 方法4:扩展运算符 + 数组方法(ES6+)

python 复制代码
js
编辑
function reverseStr(str) {
  return [...str].reverse().join('');
}

const str = "hello"; const arr = [...str]; 
// arr 的值会是:['h', 'e', 'l', 'l', 'o']
  • 优点 :比 split('') 更优雅(尤其对 emoji/Unicode 支持更好)。
  • 原理[...str] 能正确处理 UTF-16 代理对(如 '👨‍💻'.length === 5,但 [...'👨‍💻'].length === 1)。

🌰 对比:

bash 复制代码
js
编辑
'café'.split('')   // ['c','a','f','é'] ✅
'👨‍💻'.split('')    // ['👨', '‍', '💻'] ❌
[...'👨‍💻']         // ['👨‍💻'] ✅

✅ 方法5:递归实现(展示思维)

rust 复制代码
js
编辑
function reverseStr(str) {
  if (str === '') return ''; // 终止条件
  return reverseStr(str.substring(1)) + str.charAt(0);
}

const str = "hello"; 
const subStr = str.substring(1); 
// subStr 的值会是:"ello" (从索引1的 'e' 开始到末尾)

const str = "hello"; 
const firstChar = str.charAt(0); 
// firstChar 的值会是:"h" (索引0位置的字符)

1.  `reverseStr("hello")` 调用 `reverseStr("ello")`
1.  `reverseStr("ello")` 调用 `reverseStr("llo")`
1.  `reverseStr("llo")` 调用 `reverseStr("lo")`
1.  `reverseStr("lo")` 调用 `reverseStr("o")`
1.  `reverseStr("o")` 调用 `reverseStr("")`
1.  `reverseStr("")` 触发终止条件,返回 `""`
1.  `reverseStr("o")` 得到返回值 `"" + "o"`,即 `"o"`,并返回
1.  `reverseStr("lo")` 得到返回值 `"o" + "l"`,即 `"ol"`,并返回
1.  `reverseStr("llo")` 得到返回值 `"ol" + "l"`,即 `"oll"`,并返回
1.  `reverseStr("ello")` 得到返回值 `"oll" + "e"`,即 `"olle"`,并返回
1.  `reverseStr("hello")` 得到返回值 `"olle" + "h"`,即 `"olleh"`,并返回给最初的调用者
  • 优点:体现分治思想,代码简洁。

  • ⚠️ 风险

    • 爆栈:长字符串(如 >10,000 字符)会导致栈溢出;
    • 性能差 :每次 substring 都创建新字符串,时间复杂度 O(n²)。

🚫 不推荐生产使用,但面试可作为"展示递归理解"的备选。


✅ 方法6:reduce 高阶函数(函数式风格)

javascript 复制代码
js
编辑
function reverseStr(str) {
  return [...str].reduce((reversed, char) => char + reversed, '');
}
  • 优点:无状态、纯函数风格,适合函数式编程场景。
  • 缺点:可读性略低于三连 API。

📊 反转方法对比总结

方法 可读性 性能 Unicode 安全 推荐场景
split+reverse+join ⭐⭐⭐⭐ ⭐⭐⭐ ❌(部分) 快速实现
for 循环 ⭐⭐⭐ ⭐⭐⭐⭐ 兼容老环境
for...of ⭐⭐⭐⭐ ⭐⭐⭐ 现代项目
[...str] ⭐⭐⭐⭐⭐ ⭐⭐⭐ ✅✅ 首选推荐
递归 ⭐⭐ 面试展示
reduce ⭐⭐⭐ ⭐⭐ 函数式偏好

最佳实践 :日常开发优先用 [...str].reverse().join('')


🔢 二、两数之和:从暴力到哈希优化

题目 :给定整数数组 nums 和目标值 target,找出两个数的索引,使其和等于 target。假设每组输入只有一组解。

面试官真实意图:

  • 考察 时间复杂度意识
  • 是否知道 "空间换时间" 思想
  • 数据结构选择 的敏感度(Object vs Map)

❌ 解法1:暴力双重循环(O(n²))

ini 复制代码
js
编辑
function twoSum(nums, target) {
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] === target) {
        return [i, j];
      }
    }
  }
}
  • 问题:n=10⁴ 时,操作次数达 5×10⁷,明显超时。
  • 结论:仅用于兜底或教学演示。

✅ 解法2:哈希表优化(O(n))------核心思路

核心思想:

"求和变求差"

遍历时,记录每个数字及其索引;

对当前 num,检查 target - num 是否已存在。

方案A:使用普通对象(Object)

ini 复制代码
js
编辑
function twoSum(nums, target) {
  const map = {};
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (map[complement] !== undefined) {
      return [map[complement], i];
    }
    map[nums[i]] = i;
  }
}
  • 风险 :键为字符串,若 nums 含非数字(如 '2'),可能混淆;
  • 性能:V8 对对象属性访问高度优化,实际很快。

方案B:使用 Map(推荐!)

ini 复制代码
js
编辑
function twoSum(nums, target) {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (map.has(complement)) {
      return [map.get(complement), i];
    }
    map.set(nums[i], i);
  }
}
  • 优势

    • 键类型保持原样(number 还是 number);
    • has() / get() 语义清晰;
    • 避免原型链污染(如 map.__proto__ 干扰);
    • 更符合"键值对容器"语义。

📌 面试加分点 :主动说出 "我选择 Map 而非对象,因为......"


🧠 三、面试官到底想考什么?

题目 考察维度 高分回答要点
字符串反转 - API 熟练度 - 代码风格 - 边界意识 "我会优先用 [...str] 因为它对 Unicode 更安全"
两数之和 - 算法思维 - 数据结构选择 - 复杂度分析 "暴力是 O(n²),我用哈希表降到 O(n),空间换时间"

💡 避坑提示

  • 不要一上来就写递归(除非被要求);
  • 不要说"Object 和 Map 一样"(暴露基础不牢);
  • 记得处理边界(如空字符串、无解情况)。

📌 四、总结 & 复习清单

✅ 字符串反转

  • 首选写法[...str].reverse().join('')
  • 慎用递归:有爆栈风险,性能差
  • 注意 Unicodesplit('') 对 emoji 不友好

✅ 两数之和

  • 最优解Map + 一次遍历,O(n) 时间
  • 关键转换a + b = targetb = target - a
  • 不要用 Object 当哈希表(除非明确知道数据安全)
相关推荐
神秘的猪头1 小时前
🧱 深入理解栈(Stack):原理、实现与实战应用
前端·javascript·面试
StockPP2 小时前
印度尼西亚股票多时间框架K线数据可视化页面
前端·javascript·后端
心随雨下2 小时前
typescript中Triple-Slash Directives如何使用
前端·javascript·typescript
低保和光头哪个先来3 小时前
场景2:Vue Router 中 query 与 params 的区别
前端·javascript·vue.js·前端框架
q***95223 小时前
SpringMVC 请求参数接收
前端·javascript·算法
sen_shan4 小时前
《Vue项目开发实战》第八章:组件封装--vxeGrid
前端·javascript·vue.js
2***57424 小时前
Vue项目国际化实践
前端·javascript·vue.js
3秒一个大4 小时前
JavaScript 作用域:从执行机制到块级作用域的演进
javascript
星空的资源小屋4 小时前
VNote:程序员必备Markdown笔记神器
javascript·人工智能·笔记·django