例题给全套:题面、样例、参考代码、以及一步步的讲解。先自己读题想一遍, 再看代码,最后对着讲解核一遍自己想的对不对。
练习只给题面、样例和一句提示,答案是折叠起来的 —— 请先自己写一版跑通了再展开。写不出来也别直接看答案,先回速查那一组找找。
每份代码都能直接点运行:输入框里已经填好了样例输入, 把它改一改再跑,是这一页最值钱的用法。
先把地基摆一遍
string 就是「一排 char」,而且它自己知道自己有多长。你可以像数组一样 s[0]、s[i] 地取,也可以整个拿来比大小、拼接、当函数的返回值 —— 这比 C 语言那种 char s[100] 省心得多。竞赛里除非卡常,一律用它。
★ 最该先记牢的一条是「字符也是整数」:char 在 C++ 里本来就是一个小整数,'a' 是 97、'0' 是 48。所以 c - '0' 把字符 '7' 变成数字 7,c - 'a' 把字母变成 0~25 的编号 —— 后者是开计数数组 cnt[26] 的前提,第 47 章起几乎每道字符串题都要用。
⚠ 而字符串比大小是字典序,不是数值:"100" < "99" 是真的(因为 '1' < '9')。第 44 章高精度里那个「比大小要先比位数」的坑,根子就在这儿。
例题(2 道)
每道都给全套:题面 → 样例 → 参考代码 → 一步步的讲解。
例题 ① 每个字母出现了几次
给一个只由小写字母组成的字符串,统计每个字母各出现了多少次,按字母表顺序输出。
s(只含小写字母,长度 ≤ 10⁵)。输入
hello
输出
e 1 h 1 l 2 o 1
hello 里 e 一个、h 一个、l 两个、o 一个 —— 按字母序输出。开一个长度 26 的计数数组,然后……字母怎么变成下标?这一步是这一组最该记牢的一句话。
// 例题 ①「每个字母出现了几次」—— 字符串组//// 整页最该记牢的一句话在这里兑现:**字符也是整数。**// 'a' 是 97、'b' 是 98 …… 所以 c - 'a' 把 26 个字母变成 0~25 的编号,// 而这正是「开一个长度 26 的计数数组」的前提。第 47 章起几乎每道字符串题都要用。
#include <bits/stdc++.h>using namespace std;
int main() { string s; cin >> s; // >> 读到空格/换行就停 —— 它读的是「一个词」
int cnt[26] = {0}; // 26 个格子,全 0 for (char c : s) cnt[c - 'a']++; // 'h' - 'a' = 7 ⇒ 第 7 个格子加一
for (int i = 0; i < 26; i++) if (cnt[i] > 0) cout << (char)('a' + i) << ' ' << cnt[i] << "\n"; // 反过来:编号 + 'a' 再转回 char,就是那个字母本身 return 0;}点「运行 ▶」看结果
char在 C++ 里本来就是一个小整数(它存的是 ASCII 码):'a'是 97、'b'是 98、'h'是 104。- 于是
c - 'a'就把字母变成0 ~ 25的编号:'h' - 'a'= 104 − 97 = 7。⇒cnt[c - 'a']++读成「第 7 个格子加一」。 - 反过来也成立:
(char)('a' + i)把编号变回字母。⚠ 那个(char)不能省 —— 不转的话cout会把它当成一个数字打出来。 for (char c : s)就能把串里每个字符依次取出来 —— string 在这方面和 vector 一模一样。
这条 c - 'a' 是第 47 章起几乎每道字符串题的第一句。同一个道理的另一半是 c - '0':它把字符 '7' 变成数字 7。
用到的写法:c - '0' c - 'a' string s; cin >> s; s.size() / s[i] / s.back()
例题 ② "100" 到底比 "99" 大还是小
给两个不含前导零的数字串 a 和 b。请输出三样东西:
① a 的各位数字之和;② 直接用 a < b 比较(也就是字典序)的结果;③ 按数值大小比较 a < b 的结果。
⚠ 两个串都可能很长(长度 ≤ 18),但这道题不需要把它们转成数字。
a 和 b。输入
100 99
输出
1 1 0
"100" < "99" 是真的(第一位 '1' < '9' 就分出高下了),而 100 < 99 是假的。⇒ 这两件事根本不是一回事。第 ② 问一行就能写完。难的是第 ③ 问:两个数字串怎么按数值比大小?(想想什么时候一眼就能看出谁大。)
// 例题 ②「"100" 到底比 "99" 大还是小」—— 字符串组//// 字符串的 < 是**字典序**:逐位比,第一个不同的位说了算。// 于是 "100" < "99" 是**真**的(因为 '1' < '9'),而 100 < 99 是假的。// 第 44 章高精度那句「比大小要先比位数」,根子就在这儿。
#include <bits/stdc++.h>using namespace std;
// 按「数值」比较两个无前导零的数字串:先比位数,位数一样再比字典序bool numLess(const string &a, const string &b) { if (a.size() != b.size()) return a.size() < b.size(); return a < b;}
int main() { string a, b; cin >> a >> b;
int sum = 0; for (char c : a) sum += c - '0'; // '7' - '0' = 7:字符 → 它代表的数字 cout << sum << "\n";
cout << (a < b) << "\n"; // 字典序 cout << numLess(a, b) << "\n"; // 数值 return 0;}点「运行 ▶」看结果
- 各位数字之和:
for (char c : a) sum += c - '0';—— 还是那句「字符也是整数」。 - 字符串的
<是字典序:逐位比,第一个不同的位说了算;都相同时短的排前面。所以"abc" < "abd"、"ab" < "abc"。 - 按数值比就得自己写:先比位数(没有前导零 ⇒ 位数多的一定大),位数一样时字典序和数值恰好一致,这时才能直接用
<。 - ⚠ 那个函数的参数写成
const string &a—— 传字符串一律这么写:少了&就是每次调用都把整个串复制一遍。
第 44 章高精度里那句「比大小要先比位数」,根子就在这儿。⇒ 而更一般的一句话是:字符串的 < 和数字的 < 是两个不同的东西,同名而已。
用到的写法:s == t s < t c - '0' c - 'a' s.size() / s[i] / s.back()
练习(3 道)
答案折叠着,先自己写一版。
练习 ① 是不是回文
给一个字符串,判断它正着读和倒着读是不是一样的。
s(不含空格,长度 ≤ 10⁵)。Yes,否则输出 No。输入
abcba
输出
Yes
abcba 正着倒着都一样。从两头往中间走。⚠ 「倒数第 i 个」的下标是多少?(这是全书最常见的差一,写错了不一定报错,只是答案不对。)另外:循环要走到哪儿才停?走满全程会发生什么?
练习 ② 大小写互换
给一个字符串,把其中的小写字母变成大写、大写字母变成小写,其余字符原样不动,然后输出。
s(不含空格,长度 ≤ 10⁵)。输入
Hello2World
输出
hELLO2wORLD
Hello2World ⇒ hELLO2wORLD,中间那个数字 2 原样留着。'a' 是 97、'A' 是 65 —— 差正好 32。⚠ 而这道题要改串里的字符:范围 for 写成 for (char c : s) 改的是谁?
这道题真正的考点是那个 &:for (char &c : s) 改的才是串本身。少一个 &,程序照样跑、照样不报错,只是什么都没变。
练习 ③ 所有长度为 k 的子串
给一个字符串 s 和一个正整数 k,请把 s 里所有长度为 k 的连续子串按出现顺序逐行输出。
⚠ 如果 k 比 s 还长,什么都不要输出。
s 和一个整数 k(1 ≤ k ≤ 100)。输入
abcd 2
输出
ab bc cd
abcd 长度为 2 的子串有 ab、bc、cd 三个。截一段用 s.substr(i, len)。⚠ 第二个参数是长度,不是结束下标。起点 i 最大能取到哪儿?(想想最后一个子串是从哪儿开始的。)
把循环条件写成 i + k <= n 而不是 i <= n - k,就不用担心 n - k 变成负数 ——和上一组那条「别做减法」是同一个手势。