1. LeetCode 482题解析:车牌号格式化
这道题目看似简单,但实际处理起来有不少细节需要注意。题目要求我们对给定的车牌号字符串进行重新格式化,使其符合特定的分组规则。作为一名经常刷题的开发者,我发现这类字符串处理题目在实际面试中出现频率很高,特别是对边界条件的处理能力考察很严格。
题目给出的示例是这样的:输入字符串"5F3Z-2e-9-w"和k=4,输出应为"5F3Z-2E9W"。也就是说,我们需要:
- 移除所有原有的连字符
- 将所有字母转为大写
- 从字符串末尾开始,每隔k个字符插入一个连字符
- 第一组字符数量可以少于k个
2. 解题思路详解
2.1 基础处理步骤
首先我们需要对原始字符串进行预处理:
- 使用toUpperCase()将所有字母转为大写
- 使用replace()方法移除所有连字符
这个预处理步骤很关键,它简化了后续的操作。在实际编码中,我建议先将这两个操作合并成一行,这样代码更简洁:
java复制String str = S.toUpperCase().replace("-","");
2.2 反向遍历技巧
这道题最巧妙的地方在于从字符串末尾开始处理。为什么要这样做?因为从末尾开始可以更方便地控制分组:
- 创建一个StringBuilder来构建结果字符串
- 从字符串末尾开始遍历
- 每添加k个字符就插入一个连字符
- 最后将结果反转
这种反向处理的方法避免了正向处理时第一组长度不确定的问题。我在实际编码时发现,正向处理需要考虑很多边界条件,而反向处理则简单明了。
2.3 计数器使用
我们需要一个计数器来跟踪已处理的字符数:
java复制int counter = 0;
每处理一个字符,计数器加1。当计数器达到k的倍数时,插入一个连字符。但要注意最后一个字符后面不能加连字符,所以需要添加条件判断i!=0。
3. 完整代码实现
java复制class Solution {
public String licenseKeyFormatting(String S, int K) {
// 预处理:转大写并移除所有连字符
final String str = S.toUpperCase().replace("-","");
StringBuilder sb = new StringBuilder();
int counter = 0;
// 从后往前遍历
for (int i = str.length()-1; i>=0; --i) {
sb.append(str.charAt(i));
counter++;
// 每K个字符插入一个连字符,但不在开头插入
if(counter%K == 0 && i!=0) sb.append("-");
}
// 反转得到最终结果
return sb.reverse().toString();
}
}
4. 复杂度分析
让我们分析一下这个解法的时间和空间复杂度:
- 时间复杂度:O(n),其中n是字符串长度。我们需要遍历字符串两次(一次预处理,一次构建结果)
- 空间复杂度:O(n),需要使用StringBuilder存储中间结果
这个复杂度对于题目给定的约束条件来说是完全可行的。
5. 边界条件与测试用例
在实际编码中,我发现有几个边界条件需要特别注意:
- 输入字符串全是连字符的情况
- K=1的特殊情况
- 输入字符串为空的情况
- 处理后字符串长度正好是K的倍数的情况
我建议编写以下测试用例来验证代码的正确性:
java复制@Test
public void testLicenseKeyFormatting() {
Solution solution = new Solution();
assertEquals("5F3Z-2E9W", solution.licenseKeyFormatting("5F3Z-2e-9-w", 4));
assertEquals("2-5G-3J", solution.licenseKeyFormatting("2-5g-3-J", 2));
assertEquals("AA-AA", solution.licenseKeyFormatting("aaaa", 2));
assertEquals("A", solution.licenseKeyFormatting("a", 1));
assertEquals("", solution.licenseKeyFormatting("---", 3));
}
6. 常见错误与调试技巧
在解决这个问题时,我遇到过几个常见的错误:
- 忘记处理大小写转换:题目要求所有字母必须大写
- 正向处理导致第一组长度不符合要求:从前往后处理时,第一组长度可能大于k
- 在字符串开头插入多余的连字符:需要在条件中加入
i!=0的判断 - 忘记反转最终结果:因为是从后往前构建的字符串
调试这类字符串问题时,我建议:
- 在关键步骤打印中间结果
- 使用小规模的测试用例逐步验证
- 特别注意循环的边界条件
7. 算法优化思考
虽然这个解法已经足够高效,但我们还可以考虑一些优化方向:
- 能否只遍历字符串一次?
- 能否不使用StringBuilder.reverse()?
- 能否提前计算需要的StringBuilder容量?
经过尝试,我发现从前往后处理也是可行的,但需要先计算第一组的长度:
java复制int firstGroupLen = str.length() % K;
if(firstGroupLen == 0) firstGroupLen = K;
然后分别处理第一组和后续组。这种方法虽然避免了反转操作,但代码会复杂一些,可读性降低。
8. 实际应用场景
这类字符串处理问题在实际开发中很常见,比如:
- 信用卡号格式化显示
- 产品序列号分组
- 身份证号显示处理
- 电话号码格式化
掌握这种分组处理的技巧对日常开发很有帮助。我在处理用户输入的银行卡号时就用过类似的逻辑,确保显示格式符合银行规范。
9. 类似题目推荐
如果你想进一步练习类似的字符串处理题目,我推荐:
- LeetCode 6. Zigzag Conversion
- LeetCode 68. Text Justification
- LeetCode 443. String Compression
- LeetCode 820. Short Encoding of Words
这些题目都考察了对字符串的复杂处理能力,是面试中的高频考点。
10. 个人解题心得
在解决这个问题的过程中,我最大的收获是学会了反向处理字符串的技巧。这种方法在很多场景下都能简化问题,特别是当我们需要从后往前满足某些条件时。另外,StringBuilder的使用也让我意识到,在频繁修改字符串时,使用StringBuilder比直接操作String效率高得多。
一个小技巧:在处理字符串问题时,先明确需要哪些预处理步骤(如大小写转换、去除特定字符等),这能大大简化后续的逻辑。同时,一定要考虑各种边界条件,这是面试官特别看重的点。