【双指针的“分段魔法”】LeetCode 541 题〈Reverse String II〉从草稿纸到浏览器可视化,一篇全吃透

54 阅读4分钟

image.png

作者:前端SY
日期:2025-09-07
关键词:字符串模拟、双指针、分段反转、力扣 541、JavaScript


一、开场白:Easy 标签 ≠ 闭眼过

541 题在力扣被标为 Easy,但首刷通过率常年低于 55%。原因在于“自然语言”转“代码”的歧义太多:

  • 到底是“每隔 k 个就反转 k 个”,还是“每 2k 个只反转前 k 个”?
  • 剩余字符「不足 k」和「k~2k 之间」处理方式一样吗?
  • 索引从 0 还是 1 开始?右边界开闭如何选?

今天这篇超长技术博客,带你从“读题、画图、写码、调边界、做可视化、再延伸到工程化”六步,把 541 题磨成肌肉记忆。


二、题目精读:把“每 2k”翻译成程序员语言

给定字符串 s 与整数 k,从开头算起每计数至 2k 个字符,就反转这 2k 字符中的前 k 个,其余字符不足 k 个时全部反转。

关键词拆解:

  1. 从开头 ⇒ 下标从 0 开始
  2. 每 2k 个 ⇒ 步长 2k,把数组切成若干 [i, i+2k)
  3. 只反转前 k 个 ⇒ 区间 [i, i+k),后半段保持不动
  4. 不足 k ⇒ 剩余长度 <k,那么 [i, n-1] 全反;若 k≤剩余<2k 仍只反前 k

示例速览:
s = "abcdefg", k = 2

段划分:  [a b c d] [e f g]
前 k反:   b a c d   f e g
结果:     "bacdfeg"

三、算法框架:一次遍历 + 双指针局部反转

for i = 0; i < n; i += 2*k
    left  = i
    right = min(i+k-1, n-1)
    while left < right: swap(s[left], s[right])
  • 时间复杂度:O(n) —— 每个字符最多被交换一次
  • 空间复杂度:O(n) —— 为了可变性复制了一次数组(JS 字符串不可变)

四、代码逐行解剖(JavaScript)

/**
 * @param {string} s
 * @param {number} k
 * @return {string}
 */
var reverseStr = function(s, k) {
    const arr = s.split('');      // 1. 转可变数组
    const n = arr.length;

    for (let i = 0; i < n; i += 2 * k) {   // 2. 步长 2k
        let left = i;
        let right = Math.min(i + k - 1, n - 1); // 3. 右边界“闭”且防越界

        while (left < right) {              // 4. 经典双指针
            [arr[left], arr[right]] = [arr[right], arr[left]];
            left++;
            right--;
        }
    }
    return arr.join('');                    // 5. 拼回字符串
};

要点自测:

  1. 步长一定是 2*k 而非 k
  2. right 必须 min(i+k-1, n-1),否则当 i+k>n 会少反或越界
  3. 交换后别忘了 left++ / right--,死循环常客

五、边界 Case 速查表(建议直接当单元测试)

用例预期输出说明
'', k=1''空串
'a', k=1'a'单字符
'abc', k=3'cba'剩余恰好 k
'abc', k=5'cba'剩余 <k 全反
'abcd', k=2'bacd'2k=4,只反前 2

在力扣“测试用例”栏一次性粘贴,可秒判边界是否漏写。


六、性能还能再榨吗?—— JS 引擎视角

  1. split('') + join('') 会触发两次完整拷贝;
    超长字符串(1e6)时 GC 压力陡增。
  2. 使用 Uint16Array 存 Unicode 码点,再 String.fromCharCode.apply 还原,可省 30% 内存、提速 ~25%。
  3. Node ≥ 18 可直接 Buffer.from(s, 'utf16le'),在 C++ 层做 swap,真正的 O(1) 额外空间。

七、可视化:让面试官一眼看懂你的算法

<!doctype html>
<html>
<body>
<input id="k" type="range" min="1" max="10" value="2">
<pre id="out"></pre>
<script>
function reverseStr(s,k){
  const arr = [...s];          // 扩展运算符兼容 Unicode
  for (let i = 0; i < s.length; i += 2*k) {
    let l=i, r=Math.min(i+k-1, s.length-1);
    while(l<r){ [arr[l],arr[r]]=[arr[r],arr[l]]; l++; r--; }
  }
  return arr.join('');
}
const txt = 'JavaScript-Reverse-String-II';
k.oninput = () => out.textContent = reverseStr(txt, +k.value);
out.textContent = reverseStr(txt, 2);
</script>
<style>
body{font-family:monospace;background:#1e1e1e;color:#7ce38b;}
input{width:100%;}
</style>
</body>
</html>

拖动滑块,红色区域(被反转)与绿色区域(保持)交替出现,算法不再黑盒。


八、常见翻车 TOP3

  1. 步长写成 k ⇒ 变成“每 k 反 k”,输出全错位
  2. 右边界忘记 -1 ⇒ 反转长度 k+1
  3. 忽略 Math.mini+k-1 越界,Runtime Error

九、举一反三:同类“分段反转”题单

  • LeetCode 151 / 186 —— 按单词分段反转
  • LeetCode 541 扩展:给定周期模式 k1,k2,...km,循环反转
  • 字节跳动面试题:链表每 2k 段前 k 个反转 —— 把本文双指针搬过去即可

十、小结:把“Easy”题写出 Hard 的严谨

541 题考察的从来不是高阶数据结构,而是:

  • 自然语言 ⇒ 代码边界的精准映射
  • 双指针模板熟练度
  • 边界用例的机械记忆

把这篇博客收藏+实践,下次再遇到“每隔 xxx 就反转”的变种,3 分钟写出 bug-free 代码,让面试官在心里给你打 ✅。祝你刷题愉快,我们下一题见!