作者:前端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个时全部反转。
关键词拆解:
- 从开头 ⇒ 下标从 0 开始
- 每 2k 个 ⇒ 步长
2k,把数组切成若干[i, i+2k)段 - 只反转前 k 个 ⇒ 区间
[i, i+k),后半段保持不动 - 不足 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. 拼回字符串
};
要点自测:
- 步长一定是
2*k而非k! right必须min(i+k-1, n-1),否则当i+k>n会少反或越界- 交换后别忘了
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 引擎视角
split('') + join('')会触发两次完整拷贝;
超长字符串(1e6)时 GC 压力陡增。- 使用
Uint16Array存 Unicode 码点,再String.fromCharCode.apply还原,可省 30% 内存、提速 ~25%。 - 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
- 步长写成
k⇒ 变成“每 k 反 k”,输出全错位 - 右边界忘记
-1⇒ 反转长度k+1 - 忽略
Math.min⇒i+k-1越界,Runtime Error
九、举一反三:同类“分段反转”题单
- LeetCode 151 / 186 —— 按单词分段反转
- LeetCode 541 扩展:给定周期模式
k1,k2,...km,循环反转 - 字节跳动面试题:链表每 2k 段前 k 个反转 —— 把本文双指针搬过去即可
十、小结:把“Easy”题写出 Hard 的严谨
541 题考察的从来不是高阶数据结构,而是:
- 自然语言 ⇒ 代码边界的精准映射
- 双指针模板熟练度
- 边界用例的机械记忆
把这篇博客收藏+实践,下次再遇到“每隔 xxx 就反转”的变种,3 分钟写出 bug-free 代码,让面试官在心里给你打 ✅。祝你刷题愉快,我们下一题见!