面试必刷:从JS底层包装类到双指针,彻底搞懂字符串与回文算法

3 阅读5分钟

在程序员的求职面试中,字符串处理往往是算法题的“开胃菜”,但越是基础的问题,越能考察出候选人的基本功。今天,我们将结合日常笔记与实战代码,从JavaScript的底层机制出发,深入剖析面试中高频出现的字符串反转与回文判断问题,并顺藤摸瓜,带你拿下它的经典衍生题型。

揭开JS字符串的“伪装”:为什么简单类型能调方法?

在JavaScript中,字符串(String)属于简单数据类型,通常存储在栈内存中。按理说,简单数据类型是不应该拥有属性和方法的,但我们在代码中却可以顺畅地使用 str.length。这背后其实是JS引擎在底层玩的一手“包装类”魔法。

JS是一门完全面向对象的语言,为了统一面向对象写法,让代码更简洁易读,JS在底层会自动将简单数据类型包装成对象。当你访问 str.length 时,JS引擎会在底层悄悄执行 new String(str),将其变成一个String实例对象(此时可以通过 Object.prototype.toString.call(str) 验证其类型为 [object String])。这个临时对象拥有 .length 等属性和各种API,而在属性访问结束后,引擎又会“打扫战场”,将其自动销毁,重新变回简单数据类型。这就好比童话里的灰姑娘,借用魔法变成公主参加舞会,午夜钟声敲响后,又脱下水晶鞋回归平凡。

理解了这一层底层逻辑,我们再来看字符串反转,就不会对API的调用感到突兀了。

基础题:反转字符串的两种流派

反转字符串是字符串操作的基石。由于JS原生字符串没有提供 reverse() 方法(只有数组才有),我们在面试中通常会遇到两种解法:

1. API转换流(适合快速实现)
既然字符串没有 reverse,我们可以利用JS的灵活性,先通过 split('') 将字符串打散成字符数组,接着调用数组的 reverse() 方法进行反转,最后用 join('') 重新拼接成字符串。

javascript

编辑

1let str = "abc";
2const res = str.split('').reverse().join('');
3console.log(res); // "cba"

这种写法代码极其简洁,但缺点是会创建额外的数组和字符串对象,有一定的内存开销。

2. 双指针流(面试高分解法)
面试官通常更希望看到你对算法和内存的理解。利用回文/反转的对称特性,我们可以设置头尾两个指针,向中间逼近并交换字符。在JS中,由于字符串不可变,我们通常通过索引直接对比或构建新串,这种方式时间复杂度为 O(n)O(n) ,空间复杂度极低,完美契合栈内存变量的使用。

javascript

编辑

1function isPalindrome(str) {
2    const len = str.length; 
3    // 利用对称特性,只需遍历一半
4    for (let i = 0; i < len / 2; i++) {
5        if (str[i] !== str[len - i - 1]) {
6            return false;
7        }
8    }
9    return true;
10}

进阶题:最多删除一个字符的回文判断

掌握了基础反转和回文判断后,我们来看一道经典的LeetCode衍生题:给定一个非空字符串 s,最多删除一个字符,判断是否能成为回文字符串。

这道题的核心在于“容错机制”。既然允许删除一个字符,说明原字符串在对称比较时,允许出现一次“失配”。当发现 s[i] !== s[j] 时,我们面临两个选择:

  1. 删除左边的字符:验证 [i+1, j] 区间是否为回文。
  2. 删除右边的字符:验证 [i, j-1] 区间是否为回文。

只要其中一种情况成立,就说明通过一次删除可以达成目标。我们可以将“判断区间是否为回文”的逻辑抽离成一个内部函数,利用闭包或作用域复用这段逻辑。

javascript

编辑

1function validPalindrome(s) {
2    const len = s.length;
3    let i = 0, j = len - 1;
4
5    // 核心内部函数:判断指定区间 [st, ed] 是否为回文
6    function checkPalindrome(st, ed) {
7        while (st < ed) {
8            if (s[st] !== s[ed]) {
9                return false;
10            }
11            st++;
12            ed--;
13        }
14        return true;
15    }
16
17    // 双指针向中间逼近
18    while (i < j && s[i] === s[j]) {
19        i++;
20        j--;
21    }
22
23    // 遇到不匹配时,尝试跳过左字符或跳过右字符
24    // 注意:只要 [i+1, j][i, j-1] 有一个是回文即可
25    if (checkPalindrome(i + 1, j)) {
26        return true;
27    }
28    if (checkPalindrome(i, j - 1)) {
29        return true;
30    }
31
32    return false;
33}

延伸思考:JS中的this指向与函数借用

在研究上述代码时,我们不妨回顾一下JS中另一个重要概念:this 指向与 call 方法。JS中的函数也是对象,函数可以借给别人用。例如,当我们定义了一个包含 say 方法的对象 o,如果直接调用 o.say()this 指向 o;但如果我们想让 say 方法在另一个对象 obj 的上下文中运行,就可以使用 o.say.call(obj)

call 的第一个参数就是用来指定函数运行时 this 的指向。这种“借用”机制与JS底层将简单数据类型包装为对象的思想异曲同工——都是为了在运行时动态地赋予对象或函数特定的上下文与能力。

总结

从字符串的包装类机制,到API反转与双指针的取舍,再到允许一次容错的回文衍生题,字符串算法看似简单,实则处处暗藏玄机。在面试中,不仅要写出能跑通的代码,更要能向面试官清晰地表达出你对内存模型、时间/空间复杂度以及JS语言特性的深刻理解。希望这篇笔记能帮你在下一次面试中,轻松拿下字符串相关的考题!