C++ 编程进阶

CSP03-07

string 使用技巧与回文判断

课程目标

核心知识点

👩‍🏫 教师引导

上节课我们学了 string 的基础:拼接、搜索、替换、大小写转换。

今天继续进阶——学习如何"切割"字符串(提取子串),以及一个经典问题:回文判断

最后,我们用综合题来验证这两节课学到的所有 string 技能!

💡 互动复习

  • s.find("abc") 找不到时返回什么?(string::npos
  • 如何把字符串 s 全部转成大写?(for 循环 + toupper)

🎬 新知导入:提取子串

假设你有一个身份证号码字符串:"110105199001234567"

你想从中提取出生年月日(第7到第14位),怎么做?

s.substr(6, 8) 就能一次搞定!

(下标从0开始,第7位就是下标6)

✨ 这就是 substr 的魔力——精准"切割"字符串的任意一段!

提取子串 substr

string s = "hello world";

// 格式1:s.substr(pos, len) — 从 pos 开始,截取 len 个字符
string sub1 = s.substr(0, 5);   // "hello"
string sub2 = s.substr(6, 5);   // "world"

// 格式2:s.substr(pos) — 从 pos 开始截到末尾
string sub3 = s.substr(6);      // "world"

常见应用

string date = "2024-03-15";
string year  = date.substr(0, 4);   // "2024"
string month = date.substr(5, 2);   // "03"
string day   = date.substr(8, 2);   // "15"

⚠️ 注意:pos 超出字符串长度会报错!使用前要确认 pos < s.size()

字符串对称(回文)判断

方法一:双指针(推荐)

string s = "racecar";
int l = 0, r = s.size() - 1;
bool isOk = true;
while (l < r) {
    if (s[l] != s[r]) {
        isOk = false;
        break;
    }
    l++; r--;
}
cout << (isOk ? "YES" : "NO") << endl;

🔍 双指针原理:分别从字符串的两端向中间移动,比较对应位置的字符是否相同。

方法二:逆序字符串比较

string s = "racecar";
string rev = s;
reverse(rev.begin(), rev.end());  // 需要 #include <algorithm>
cout << (s == rev ? "YES" : "NO") << endl;

💡 reverse(s.begin(), s.end()) 可以直接将 string 原地逆序!

双指针优点

  • 空间复杂度 O(1)
  • 时间复杂度 O(n/2)
  • 提前终止

逆序比较优点

  • 代码简洁
  • 易于理解
  • 适用于复杂场景

在 string 中使用 begin/end 迭代器

string 支持 C++ 标准库算法,可以使用迭代器范围:

#include <algorithm>
string s = "hello";
reverse(s.begin(), s.end());   // s = "olleh"
sort(s.begin(), s.end());      // s = "ehllo"(排序字符)

常用算法

  • sort() - 排序
  • reverse() - 逆序
  • find() - 查找

高级算法

  • count() - 计数
  • replace() - 替换
  • transform() - 转换

字符串综合操作技巧

删除字符串中所有指定字符

string s = "a1b2c3";
string result = "";
for (char c : s) {
    if (!isdigit(c)) result += c;  // 只保留非数字
}
// result = "abc"

字符串反转每个单词

思路:分割 → 反转每个单词 → 重新拼接

string line = "hello world";
// 使用字符串流分割单词
istringstream ss(line);
string word;
while (ss >> word) {
    reverse(word.begin(), word.end());
    // ... 重新拼接
}

⚡ GESP 真题演练

抢答题 1(选择题)

string s = "abcdef"; cout << s.substr(2, 3); 的输出是?

A. "abc" B. "bcd" C. "cde" D. "def"

答案:C | 解析:从下标 2(字符 'c')开始,截取 3 个字符:"cde"。

判断题 2

判断回文串,可以将字符串逆序后与原字符串比较,相同则是回文。( )

✓(正确)

🎈 课堂练习:提取子串

题目描述

输入字符串 s 和两个整数 l、r(1-indexed),输出 s 的第 l 到第 r 个字符(含边界)。

输入格式

第一行字符串 s(不含空格)
第二行两整数 l r

输出格式

提取的子串

样例

输入:

helloworld
2 6

输出:

ellow

🎈 课堂练习:判断回文串

题目描述

输入一个字符串,判断它是否是回文串(忽略大小写)。

输入格式

一行字符串(不含空格,长度 ≤ 1000)

输出格式

YESNO

样例输入

Racecar

样例输出

YES

🔥 进阶练习:最长回文子串

题目描述

给定一个字符串 s(只含小写字母),求其中最长的回文子串。如有多个长度相同的,输出字典序最小的。

输入格式

一行字符串 s(长度 ≤ 500)

输出格式

最长回文子串

样例输入: babad

样例输出: aba

📌 算法思路

枚举所有起始位置 i 和长度 len,提取子串,判断是否回文。

时间复杂度 O(n²),对 n≤1000 的字符串完全足够。

🌟 课堂总结

今天完成了 string 的进阶学习!

🔤
s.substr(pos, len)

精准提取子串

🔄
reverse()

原地逆序

✨ 这两节课加起来,string 的重要操作已经全部掌握!

下节课开始进入全新主题:算法篇

从"枚举算法"开始,学习信息学竞赛的思维方法!

📝 课后作业

作业 1:提取域名

输入一个 URL,提取其中的域名(在 "://" 之后到 "/" 或末尾之前)。

样例: https://www.example.com/page → www.example.com

作业 2:字符串旋转

输入字符串 s 和整数 k,将 s 向左旋转 k 位(前 k 字符移到末尾)。

样例: abcde, 2 → cdeab

作业 3:统计回文子串个数

输入字符串 s,统计其中长度 ≥ 2 的回文子串的数量。

样例: aaa → 3("aa"×2 + "aaa"×1)

← → 空格键导航