理解 JavaScript 字符串的不可变性及其对算法实现的影响
掌握常用字符串方法的实际应用
能够使用 JavaScript 高效解决简单、中等、困难级别的字符串算法题
熟悉经典字符串算法的基本思想与应用场景
在 JavaScript 中,字符串是原始类型且不可变。这意味着:
字符串一旦创建,其内容无法被修改
所有“修改”操作(如替换、截取)都会返回一个新字符串
可通过索引访问字符(str[i]),但不能赋值(str[0] = 'x' 无效)
let s = "Dev";
console.log(s[0]); // "D"
console.log(s.length); // 3
// 尝试修改(无效)
s[0] = 'X';
console.log(s); // 仍然是 "Dev"提示:由于不可变性,频繁拼接字符串(如循环中
str += char)可能导致性能问题,建议使用数组 +join()优化。
注意:
slice()和substring()在处理负数时行为不同:
slice(-2)表示从倒数第 2 位开始
substring(-2)会被视为substring(0)
function reverseString(s) {
return s.split('').reverse().join('');
}
// 或使用双指针(适用于字符数组)function isPalindrome(s) {
const clean = s.toLowerCase().replace(/[^a-z0-9]/g, '');
return clean === clean.split('').reverse().join('');
}function isSubsequence(s, t) {
let i = 0;
for (let char of t) {
if (char === s[i]) i++;
}
return i === s.length;
}function isAnagram(s1, s2) {
if (s1.length !== s2.length) return false;
const sorted1 = s1.split('').sort().join('');
const sorted2 = s2.split('').sort().join('');
return sorted1 === sorted2;
}atoi(字符串转整数)需处理空格、正负号、溢出:
function myAtoi(s) {
const num = parseInt(s.trim());
if (isNaN(num)) return 0;
const INT_MAX = 2**31 - 1;
const INT_MIN = -(2**31);
return Math.min(Math.max(num, INT_MIN), INT_MAX);
}滑动窗口
function lengthOfLongestSubstring(s) {
const set = new Set();
let left = 0, maxLen = 0;
for (let right = 0; right < s.length; right++) {
while (set.has(s[right])) {
set.delete(s[left++]);
}
set.add(s[right]);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}注意:子序列 ≠ 子串(不要求连续)
用于高效查找模式串在文本中的位置,时间复杂度 O(n + m):
function computeLPS(pattern) {
const lps = new Array(pattern.length).fill(0);
let len = 0, i = 1;
while (i < pattern.length) {
if (pattern[i] === pattern[len]) {
lps[i++] = ++len;
} else {
if (len > 0) len = lps[len - 1];
else lps[i++] = 0;
}
}
return lps;
}
function KMPSearch(text, pattern) {
const lps = computeLPS(pattern);
let i = 0, j = 0;
while (i < text.length) {
if (text[i] === pattern[j]) {
i++; j++;
if (j === pattern.length) return i - j; // 找到匹配
} else {
if (j > 0) j = lps[j - 1];
else i++;
}
}
return -1; // 未找到
}基于哈希的字符串匹配,适用于多模式匹配或大文本搜索。
使用栈或动态规划解决括号匹配问题。
避免频繁字符串拼接:使用 Array.join() 代替 +=
// 不推荐
let result = '';
for (let c of str) result += c;
// 推荐
let arr = [];
for (let c of str) arr.push(c);
let result = arr.join('');正则表达式慎用:虽然简洁,但在高频调用中可能成为性能瓶颈
利用内置方法:如 includes()、startsWith() 比手动循环更高效且可读性高
JavaScript 字符串是不可变的原始类型,所有操作返回新字符串
熟练掌握 split, join, slice, replace, includes 等方法是解决 DSA 问题的基础
简单问题常通过双指针、哈希表、排序解决
中等问题多涉及滑动窗口、动态规划、状态机
困难问题需掌握KMP、Rabin-Karp、高级 DP 等经典算法
注意边界条件(空串、单字符、全相同字符等)和性能优化
为什么 s.split('').reverse().join('') 能反转字符串?如果字符串包含 Unicode 表情符号(如 "👨💻"),这种方法是否仍然安全?为什么?
在“无重复字符的最长子串”问题中,为什么使用 Set 而不是 Array.includes() 来检查重复?时间复杂度有何差异?
KMP 算法中的 LPS(最长前缀后缀)数组是如何帮助跳过不必要的比较的?请用 "ABABCABABA" 作为模式串手动计算其 LPS 数组。