1. 题目背景与需求解析
这道来自东华OJ的基础编程题看似简单,却蕴含着字符串处理的经典套路。题目要求我们编写一个C++程序,实现从给定字符串中删除所有指定的字符。比如输入字符串"hello world"和字符'l',程序应该输出"heo word"。
在实际开发中,类似的需求比比皆是:从用户输入中过滤敏感词、清理数据中的特殊符号、处理日志文件中的干扰字符等等。这道题正是这类场景的简化模型,考察的是对字符串基础操作的掌握程度。
新手常见误区:很多初学者会直接尝试在原字符串上修改,这在C++中极易引发越界访问或迭代器失效问题。正确的做法是创建新字符串存储结果。
2. 核心算法设计思路
2.1 双指针原地修改法
对于C++这种可以直接操作内存的语言,最高效的方式是使用双指针原地修改:
cpp复制void removeChar(string &s, char c) {
int slow = 0;
for(int fast = 0; fast < s.size(); ++fast) {
if(s[fast] != c) {
s[slow++] = s[fast];
}
}
s.resize(slow);
}
这个算法的精妙之处在于:
- fast指针遍历原字符串
- slow指针指向下一个有效字符的存储位置
- 时间复杂度O(n),空间复杂度O(1)
2.2 字符串拼接法
更直观但效率稍低的方法是构建新字符串:
cpp复制string removeChar(string s, char c) {
string result;
for(char ch : s) {
if(ch != c) {
result += ch;
}
}
return result;
}
这种方法虽然多用了O(n)空间,但代码更易读,适合作为教学示例。
3. 完整实现与边界处理
3.1 基础版本实现
结合题目要求的输入输出格式,完整解法如下:
cpp复制#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
char target;
getline(cin, str); // 读取整行,包括空格
cin >> target;
string result;
for(char c : str) {
if(c != target) {
result += c;
}
}
cout << result << endl;
return 0;
}
3.2 必须考虑的边界情况
- 空字符串输入:程序应该正常处理而不崩溃
- 目标字符不存在:应原样输出字符串
- 连续多个目标字符:需要全部删除
- 字符串全为目标字符:应输出空字符串
- 大小写敏感:'a'和'A'被视为不同字符
4. 性能优化与进阶技巧
4.1 避免频繁内存分配
字符串拼接法的优化版本:
cpp复制string removeChar(const string &s, char c) {
string result;
result.reserve(s.size()); // 预分配空间
for(char ch : s) {
if(ch != c) {
result += ch;
}
}
return result;
}
使用reserve()预先分配足够空间,避免多次扩容带来的性能损耗。
4.2 使用STL算法实现
C++标准库提供了更简洁的实现方式:
cpp复制#include <algorithm>
string removeChar(string s, char c) {
s.erase(remove(s.begin(), s.end(), c), s.end());
return s;
}
这里运用了erase-remove惯用法,是C++标准库处理这类问题的黄金组合。
5. 测试用例设计
完整的测试应该包含以下场景:
| 测试用例 | 输入字符串 | 目标字符 | 预期输出 |
|---|---|---|---|
| 普通情况 | "hello" | 'l' | "heo" |
| 连续字符 | "aaabbb" | 'a' | "bbb" |
| 全目标字符 | "xxxx" | 'x' | "" |
| 空字符串 | "" | 'a' | "" |
| 包含空格 | "a b c" | ' ' | "abc" |
| 大小写混合 | "Hello" | 'h' | "Hello" |
6. 常见错误与调试技巧
6.1 迭代器失效问题
错误示例:
cpp复制for(auto it = str.begin(); it != str.end(); ++it) {
if(*it == target) {
str.erase(it); // 危险!erase会使迭代器失效
}
}
正确做法是使用erase的返回值:
cpp复制for(auto it = str.begin(); it != str.end(); ) {
if(*it == target) {
it = str.erase(it);
} else {
++it;
}
}
6.2 未处理换行符
当使用cin >> str读取字符串时,会停在第一个空白字符处。如果输入是"hello world"和'l',用cin只会读取到"hello"。必须使用getline()读取整行输入。
7. 实际应用场景扩展
这个简单的算法可以延伸出许多实用功能:
- 敏感词过滤:遍历敏感词列表,逐个从文本中删除
- 数据清洗:移除CSV文件中的非法字符
- 密码复杂度检查:检查是否包含特定字符类型
- 日志处理:过滤调试信息中的噪音字符
例如实现一个简单的敏感词过滤器:
cpp复制string filterSensitiveWords(string text, const vector<string>& words) {
for(const auto& word : words) {
for(char c : word) {
text.erase(remove(text.begin(), text.end(), c), text.end());
}
}
return text;
}
8. 不同语言的实现对比
了解C++实现后,看看其他语言的写法也很有启发:
Python实现:
python复制def remove_char(s, c):
return s.replace(c, '')
Java实现:
java复制public static String removeChar(String s, char c) {
return s.replaceAll(Pattern.quote(String.valueOf(c)), "");
}
JavaScript实现:
javascript复制function removeChar(str, char) {
return str.split(char).join('');
}
相比之下,C++版本虽然代码量稍大,但性能最优,且能更深入理解底层原理。这也是OJ题目通常要求用C++实现的原因——更能考察基础功底。