1. 字符串基础操作实战:反转与替换
字符串处理是编程中最基础也最常遇到的任务之一。今天我想分享几个字符串处理的经典问题及其解决方案,这些问题看似简单,但其中蕴含着不少值得注意的细节和优化技巧。
2. 344. 字符串反转问题
2.1 问题描述与直观解法
给定一个字符串如"abcd",我们需要将其反转为"dcba"。这是最基础的字符串反转问题,也是理解字符串操作的入门案例。
最直观的解法是创建一个新的字符串,然后从后向前遍历原字符串,将字符逐个添加到新字符串中:
java复制public String reverseString(String s) {
StringBuilder sb = new StringBuilder();
for (int i = s.length() - 1; i >= 0; i--) {
sb.append(s.charAt(i));
}
return sb.toString();
}
这种方法的时间复杂度是O(n),空间复杂度也是O(n),因为我们需要额外的空间来存储反转后的字符串。
2.2 双指针优化解法
更高效的解法是使用双指针技术,在原字符串上进行原地反转:
java复制public void reverseString(char[] s) {
int left = 0, right = s.length - 1;
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
这种方法的时间复杂度仍然是O(n),但空间复杂度优化到了O(1),因为我们只使用了常数级别的额外空间。
注意:在Java中,字符串是不可变的,所以我们需要先将字符串转换为字符数组才能进行原地修改。
2.3 边界条件与测试用例
在实际编码中,我们需要考虑各种边界条件:
- 空字符串
- 单字符字符串
- 包含特殊字符的字符串
- 非常长的字符串(性能测试)
3. 541. 反转字符串 II
3.1 问题理解与规则解析
这个问题比基础反转要复杂一些。给定一个字符串s和一个整数k,我们需要:
- 从字符串开头算起,每2k个字符为一组
- 对于每组,反转前k个字符
- 如果剩余字符少于k个,则全部反转
- 如果剩余字符在k到2k之间,只反转前k个
例如,对于s = "abcdefg", k = 2:
- 第一组4个字符("abcd"),反转前2个→"bacd"
- 剩余3个字符("efg"),多于k(2)但少于2k(4),反转前2个→"efg"不变
- 最终结果:"bacdfeg"
3.2 实现思路与代码
我们可以通过分段处理来实现这个逻辑:
java复制public String reverseStr(String s, int k) {
char[] arr = s.toCharArray();
for (int i = 0; i < arr.length; i += 2 * k) {
int left = i;
int right = Math.min(i + k - 1, arr.length - 1);
while (left < right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
return new String(arr);
}
3.3 关键点与易错点
- 边界处理:特别注意剩余字符不足k或不足2k的情况
- 反转范围:确保right指针不超过数组边界
- 步长选择:每次循环后i增加2k,不是k
- 性能考虑:转换为字符数组操作比直接操作字符串更高效
4. 卡码网54题:替换数字
4.1 问题分析与初步解法
给定一个包含小写字母和数字的字符串,需要将所有数字替换为"number"。例如"a1b2c3"→"anumberbnumbercnumber"。
最直接的方法是遍历字符串,遇到数字就替换:
java复制public String replaceDigits(String s) {
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
sb.append("number");
} else {
sb.append(c);
}
}
return sb.toString();
}
这种方法简单直观,但当字符串中有大量数字时,会创建很多临时字符串,效率不高。
4.2 性能优化方案
我们可以预先计算最终字符串的长度,避免多次扩容:
java复制public String replaceDigits(String s) {
int digitCount = 0;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) digitCount++;
}
int newLength = s.length() + digitCount * ("number".length() - 1);
StringBuilder sb = new StringBuilder(newLength);
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
sb.append("number");
} else {
sb.append(c);
}
}
return sb.toString();
}
4.3 字符判断的多种方式
判断一个字符是否是数字有几种方法:
Character.isDigit(c)c >= '0' && c <= '9'- 使用正则表达式
第一种方法最清晰且支持Unicode数字字符,推荐使用。
5. 字符串处理中的常见陷阱与优化技巧
5.1 字符串拼接的性能问题
在Java中,字符串是不可变对象,直接使用"+"拼接字符串会创建大量临时对象。对于频繁拼接操作,应该使用StringBuilder。
经验法则:在循环内拼接字符串时,总是使用StringBuilder。
5.2 字符与字符串的转换
- 字符串转字符数组:
s.toCharArray() - 字符数组转字符串:
new String(charArray) - 单个字符转字符串:
Character.toString(c)或String.valueOf(c)
5.3 边界条件处理
字符串问题中常见的边界条件包括:
- 空字符串或null输入
- 字符串长度刚好等于k或2k的倍数
- 字符串包含特殊字符(空格、标点等)
- 非常大的字符串(内存考虑)
5.4 测试用例设计
好的测试用例应该覆盖:
- 正常情况
- 边界情况
- 极端情况
- 特殊字符情况
例如对于反转字符串II问题:
- s="abcdefg", k=2 → "bacdfeg"
- s="abcd", k=2 → "bacd"
- s="a", k=1 → "a"
- s="", k=1 → ""
- s="abcdefgh", k=3 → "cbadefhg"
6. 扩展思考与实际应用
6.1 类似问题的变种
- 反转字符串中的单词顺序(保留空格)
- 旋转字符串(如将"abcdef"旋转2位得到"cdefab")
- 字符串压缩(如"aabcccccaaa"→"a2b1c5a3")
6.2 实际应用场景
- 文本编辑器中的反转功能
- 数据清洗中的字符替换
- 加密算法中的字符位置变换
- 日志处理中的格式转换
6.3 算法选择与性能考量
对于不同规模的字符串处理:
- 小字符串:简单直观的方法即可
- 大字符串:需要考虑内存使用和算法效率
- 高频操作:可能需要更复杂的算法或预处理
在实际项目中,除了算法效率,代码可读性和维护性同样重要。有时候牺牲一点性能换取更清晰的代码是值得的。