string - 字符串处理
std::string 是 C++ 中最常用的字符串类,提供了丰富的字符串操作方法。在算法竞赛中,字符串处理是一个常见的考点,掌握 string 的使用技巧能大大提升解题效率。
竞赛中的核心考点
| 操作 | 时间复杂度 | 竞赛要点 |
|---|
| 访问单个字符 | O(1) | 随机访问 |
| 字符串拼接 | O(n) | 注意内存分配 |
| 子串提取 | O(n) | 提取子字符串 |
| 查找 | O(n) | 查找子串或字符 |
| 替换 | O(n) | 替换子串 |
| 插入/删除 | O(n) | 注意性能影响 |
| 比较 | O(n) | 字典序比较 |
一、基本用法
1.1 初始化
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s1;
string s2 = "hello";
string s3("world");
string s4(5, 'a');
const char* c_str = "test";
string s5(c_str);
string s6(s2);
string s7(s2, 1, 3);
string s8(s2.begin(), s2.end());
return 0;
}
1.2 基本操作
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s = "hello";
char c = s[0];
char d = s.at(1);
s[0] = 'H';
int len = s.size();
int length = s.length();
bool is_empty = s.empty();
s.clear();
int cap = s.capacity();
s.reserve(100);
return 0;
}
二、常用方法
2.1 字符串拼接
string s = "hello";
s += " world";
s.append("!");
s.append(3, '?');
string s1 = "hello";
string s2 = "world";
string s3 = s1 + " " + s2;
s.insert(s.end(), '!');
2.2 子串操作
string s = "Hello, world!";
string sub1 = s.substr(7, 5);
string sub2 = s.substr(7);
size_t pos = s.find("world");
if (pos != string::npos)
{
cout << "Found at position: " << pos << endl;
}
pos = s.find('o');
pos = s.find('o', 5);
pos = s.rfind('o');
pos = s.find_first_of("aeiou");
pos = s.find_first_not_of("Helo ");
2.3 替换操作
string s = "Hello, world!";
s.replace(7, 5, "C++");
s.replace(s.begin() + 7, s.end() - 1, "programming");
string s = "ababab";
size_t pos = 0;
while ((pos = s.find("ab", pos)) != string::npos)
{
s.replace(pos, 2, "CD");
pos += 2;
}
2.4 插入和删除
string s = "Hello";
s.insert(5, " world");
s.insert(s.begin(), '!');
s.erase(s.begin());
s.erase(5, 6);
s.erase(s.begin() + 2, s.end());
三、字符串比较
string s1 = "apple";
string s2 = "banana";
if (s1 < s2)
{
cout << "s1 comes before s2" << endl;
}
int result = s1.compare(s2);
if (result < 0) cout << "s1 < s2" << endl;
else if (result == 0) cout << "s1 == s2" << endl;
else cout << "s1 > s2" << endl;
result = s1.compare(0, 3, s2, 0, 3);
四、C++11 现代特性
4.1 字符串前缀和后缀检查
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
bool starts_with(const string& s, const string& prefix)
{
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}
bool starts_with(const string& s, char c)
{
return !s.empty() && s[0] == c;
}
bool ends_with(const string& s, const string& suffix)
{
return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
}
bool ends_with(const string& s, char c)
{
return !s.empty() && s.back() == c;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s = "Hello, world!";
if (starts_with(s, "Hello"))
{
cout << "Starts with Hello" << endl;
}
if (starts_with(s, 'H'))
{
cout << "Starts with H" << endl;
}
if (ends_with(s, "!"))
{
cout << "Ends with !" << endl;
}
if (ends_with(s, "world!"))
{
cout << "Ends with world!" << endl;
}
return 0;
}
4.2 字符串包含检查
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
bool contains(const string& s, const string& substr)
{
return s.find(substr) != string::npos;
}
bool contains(const string& s, char c)
{
return s.find(c) != string::npos;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s = "Hello, world!";
if (contains(s, "world"))
{
cout << "Contains 'world'" << endl;
}
if (contains(s, 'o'))
{
cout << "Contains 'o'" << endl;
}
return 0;
}
五、字符串输入读取
核心要点
| 场景 | 方法 | 关键代码 |
|---|
| 单个单词 | cin >> s | 遇空格停止 |
| 整行读取 | getline(cin, s) | 包含空格 |
| 数字+字符串 | 先 cin >> n 再 getline | 必须 cin.ignore() |
关键代码示例
int n;
cin >> n;
cin.ignore();
string s;
getline(cin, s);
int n; cin >> n; cin.ignore();
while (n--)
{
string line;
getline(cin, line);
}
string line;
while (getline(cin, line))
{
}
六、竞赛实战技巧
5.1 字符串反转
string reverse_string(string s)
{
reverse(s.begin(), s.end());
return s;
}
void reverse_inplace(string& s)
{
int n = s.size();
for (int i = 0; i < n / 2; i++)
{
swap(s[i], s[n - 1 - i]);
}
}
5.2 字符串分割
vector<string> split(const string& s, char delimiter)
{
vector<string> tokens;
string token;
istringstream tokenStream(s);
while (getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
vector<string> split_whitespace(const string& s)
{
vector<string> tokens;
string token;
istringstream tokenStream(s);
while (tokenStream >> token)
{
tokens.push_back(token);
}
return tokens;
}
5.3 字符串转数字
int str_to_int(const string& s)
{
return stoi(s);
}
long long str_to_ll(const string& s)
{
return stoll(s);
}
double str_to_double(const string& s)
{
return stod(s);
}
string int_to_str(int n)
{
return to_string(n);
}
string ll_to_str(long long n)
{
return to_string(n);
}
5.4 回文串判断
bool is_palindrome(const string& s)
{
int n = s.size();
for (int i = 0; i < n / 2; i++)
{
if (s[i] != s[n - 1 - i])
{
return false;
}
}
return true;
}
bool is_palindrome2(const string& s)
{
int left = 0, right = s.size() - 1;
while (left < right)
{
if (s[left] != s[right])
{
return false;
}
left++;
right--;
}
return true;
}
5.5 子串查找
vector<int> compute_lps(const string& pattern)
{
int n = pattern.size();
vector<int> lps(n, 0);
for (int i = 1, len = 0; i < n; )
{
if (pattern[i] == pattern[len])
{
lps[i++] = ++len;
}
else if (len > 0)
{
len = lps[len - 1];
}
else
{
lps[i++] = 0;
}
}
return lps;
}
vector<int> kmp_search(const string& text, const string& pattern)
{
vector<int> result;
int n = text.size(), m = pattern.size();
if (m == 0) return result;
vector<int> lps = compute_lps(pattern);
for (int i = 0, j = 0; i < n; )
{
if (pattern[j] == text[i])
{
i++;
j++;
if (j == m)
{
result.push_back(i - j);
j = lps[j - 1];
}
}
else if (j > 0)
{
j = lps[j - 1];
}
else
{
i++;
}
}
return result;
}
六、性能优化
6.1 预分配空间
string s;
for (int i = 0; i < 1e5; i++)
{
s += 'a';
}
string s;
s.reserve(1e5);
for (int i = 0; i < 1e5; i++)
{
s += 'a';
}
6.2 避免不必要的拷贝
string process_string(string s)
{
return s;
}
string process_string(const string& s)
{
string result = s;
return result;
}
string create_large_string()
{
string s(1e5, 'a');
return s;
}
6.3 字符串拼接优化
string s;
for (int i = 0; i < 1000; i++)
{
s += to_string(i);
}
ostringstream oss;
for (int i = 0; i < 1000; i++)
{
oss << i;
}
string s = oss.str();
string s;
s.reserve(10000);
for (int i = 0; i < 1000; i++)
{
s += to_string(i);
}
七、常见错误与注意事项
| 错误 | 说明 | 解决方案 |
|---|
| 越界访问 | 访问超出字符串长度的索引 | 使用 at() 或先检查长度 |
| 字符串拼接性能 | 频繁拼接导致多次内存分配 | 使用 reserve() 或 ostringstream |
| 比较字符串和字面量 | 直接比较可能导致意外结果 | 确保类型匹配 |
| 字符编码问题 | 处理非 ASCII 字符时出错 | 注意编码格式,使用 wstring 处理宽字符 |
| 内存使用过高 | 存储过长的字符串 | 考虑使用 string_view 或流式处理 |
| 查找返回值处理 | 未检查 string::npos | 总是检查查找结果 |
八、C++11 完整竞赛模板
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
vector<string> split(const string& s, char delimiter)
{
vector<string> tokens;
string token;
istringstream tokenStream(s);
while (getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
bool starts_with(const string& s, const string& prefix)
{
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}
bool ends_with(const string& s, const string& suffix)
{
return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
}
bool contains(const string& s, const string& substr)
{
return s.find(substr) != string::npos;
}
bool is_palindrome(const string& s)
{
int left = 0, right = s.size() - 1;
while (left < right)
{
if (s[left] != s[right])
{
return false;
}
left++;
right--;
}
return true;
}
vector<int> kmp_search(const string& text, const string& pattern)
{
vector<int> result;
int n = text.size(), m = pattern.size();
if (m == 0) return result;
vector<int> lps(m, 0);
for (int i = 1, len = 0; i < m; )
{
if (pattern[i] == pattern[len])
{
lps[i++] = ++len;
}
else if (len > 0)
{
len = lps[len - 1];
}
else
{
lps[i++] = 0;
}
}
for (int i = 0, j = 0; i < n; )
{
if (pattern[j] == text[i])
{
i++;
j++;
if (j == m)
{
result.push_back(i - j);
j = lps[j - 1];
}
}
else if (j > 0)
{
j = lps[j - 1];
}
else
{
i++;
}
}
return result;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
getline(cin, s);
vector<string> tokens = split(s, ' ');
for (const string& token : tokens)
{
cout << token << endl;
}
string palindrome_candidate = "racecar";
if (is_palindrome(palindrome_candidate))
{
cout << palindrome_candidate << " is a palindrome" << endl;
}
string text = "ABABDABACDABABCABAB";
string pattern = "ABABCABAB";
vector<int> positions = kmp_search(text, pattern);
cout << "Pattern found at positions: ";
for (int pos : positions)
{
cout << pos << " ";
}
cout << endl;
string test = "Hello, C++11!";
if (starts_with(test, "Hello"))
{
cout << "Starts with Hello" << endl;
}
if (ends_with(test, "!"))
{
cout << "Ends with !" << endl;
}
if (contains(test, "C++"))
{
cout << "Contains 'C++'" << endl;
}
return 0;
}
总结:竞赛要点
- 基本操作:掌握字符串的基本操作,如访问、修改、拼接、查找等
- 性能优化:使用
reserve() 预分配空间,避免频繁拼接
- 现代 C++11:使用 auto 类型推导、范围 for 循环、lambda 表达式和自定义字符串检查函数
- 实战技巧:字符串分割、反转、回文判断、KMP 算法等
- 类型转换:字符串与数字之间的转换
- 内存管理:避免不必要的拷贝,使用引用和移动语义
- 边界处理:注意字符串索引的边界检查
- 编码问题:处理特殊字符和编码格式
string 是算法竞赛中最基础的工具之一,掌握它的使用技巧能让你在处理字符串相关问题时更加得心应手。