算法题 Day6---String类(3)
目录
题7 : 判断字符串是否回文
法一:
法二:
法三:
题8 : 手机
题9 : 口算练习题
注意事项:
函数补充:
to_string 函数
stoi 函数
总结:
题7 : 判断字符串是否回文
法一:
#include<iostream>
#include<string>
using namespace std;int main()
{string s;cin >> s;size_t left = 0;size_t right = s.size() - 1;while (left < right){if (s[left] == s[right]){left++;right--;}else{cout << "no" << endl;return 0;}}cout << "yes" << endl;
}
过程:
这段代码的思路是通过双指针从两端向中间遍历比较,来判断字符串是否为回文,具体过程如下:
- 1. 输入与初始化:首先读取输入的字符串 s ,然后定义两个指针 left (初始指向字符串的第一个字符,索引为0)和 right (初始指向字符串的最后一个字符,索引为 s.size() - 1 )。
- 2. 双指针遍历比较:进入 while 循环,循环条件是 left < right (只要左指针位置在右指针左边,就继续比较)。
- - 在循环内,比较 s[left] 和 s[right] 是否相等:
- - 如果相等,说明当前这对字符符合回文要求,将 left 右移一位( left++ ), right 左移一位( right-- ),继续比较下一对字符。
- - 如果不相等,说明字符串不是回文,直接输出 no 并结束程序( return 0 )。
- 3. 循环结束与结果输出:当 while 循环正常结束(即 left >= right ),说明所有对应的字符对都相等,字符串是回文,此时输出 yes 。
法二:
#include<iostream>
#include<string>
using namespace std;int main()
{string s;cin >> s;size_t left = 0;size_t right = s.size() - 1;int n = 1;while (left <= right){if (s[left] != s[right]){n = 0;break;}left++;right--;}if (n == 1){cout << "yes" << endl;}else{cout << "no" << endl;}return 0;
}
这段代码的思路是通过双指针从两端向中间遍历,全程检查所有对应字符对,来判断字符串是否为回文,具体过程如下:
- 1. 输入与初始化:
- - 读取输入的字符串 s ;
- - 定义两个指针 left (初始指向字符串第一个字符,索引为 0 )和 right (初始指向字符串最后一个字符,索引为 s.size() - 1 );
- - 定义变量 n 并初始化为 1 ,用于标记是否为回文( 1 表示是回文, 0 表示不是)。
- 2. 双指针遍历检查所有字符对:
- - 进入 while 循环,循环条件是 left <= right (确保遍历到所有需要比较的字符对,包括字符串长度为奇数时中间的单个字符);
- - 在循环内,比较 s[left] 和 s[right] 是否不相等:
- - 如果不相等,说明字符串不是回文,将 n 设为 0 ,并通过 break 退出循环;
- - 如果相等,将 left 右移一位( left++ ), right 左移一位( right-- ),继续比较下一对字符。
- 3. 根据标记输出结果:
- - 循环结束后,判断 n 的值:
- - 若 n == 1 ,说明所有字符对都相等,字符串是回文,输出 yes ;
- - 若 n == 0 ,说明存在字符对不相等,字符串不是回文,输出 no 。
法三:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;int main()
{string s1;cin >> s1;string s2 = s1;reverse(s2.begin(), s2.end());if (s1 == s2){cout << "yes" << endl;}else{cout << "no" << endl;}return 0;
}
这段代码采用的是“反转对比法”来判断字符串是否为回文,其核心逻辑基于“回文的正序和逆序完全一致”这一特性,具体细节如下:
- 步骤1:读取原字符串
- 通过 cin >> s1 获取输入的字符串,例如输入 abcba ,此时 s1 存储为 "abcba" 。
- 步骤2:复制并反转字符串
- - 先创建 s2 并赋值为 s1 的副本( string s2 = s1; ),此时 s2 也为 "abcba" ;
- - 调用标准库函数 reverse ,将 s2 的字符顺序反转。 reverse 的参数是迭代器区间( s2.begin() 到 s2.end() ),作用是交换区间内的字符顺序。执行后, s2 变为 "abcba" 的逆序,即 "abcba" (因为原字符串本身是回文)。
- 步骤3:比较原字符串与反转后的字符串
- 通过 if (s1 == s2) 判断两者是否完全相同:
- - 若相同(如上述例子),说明原字符串是回文,输出 yes ;
- - 若不同(例如输入 abcd ,反转后为 dcba ,与原字符串不等),说明不是回文,输出 no 。
注意事项:
- string 类本身没有提供名为 reverse 的成员函数,我们代码中用的 reverse 是C++标准库的通用算法函数,而非 string 的成员,因此必须包含 <algorithm> 头文件才能使用。
- 当需要保留原始数据的副本时,或者在后续的操作中可能需要参考原始数据,先拷贝一份可以避免原始数据被覆盖或修改。
- reverse 是原地操作函数。它的工作方式是直接修改传入的容器/数据结构本身(通过迭代器指定的区间),不会创建新的副本。例如对字符串 s1 调用 reverse(s1.begin(), s1.end()) 后, s1 自身的字符顺序会被直接反转,原有的字符排列会被覆盖,整个过程不会额外生成新的字符串对象。
补充:
好的,结合“直接在原对象上修改、无需复制到新对象”的核心标准,以下是 C++ 中符合该要求的常用函数分类汇总,涵盖字符串、容器、通用算法等场景,每个类别下明确函数作用和修改特点:
一、字符串类( std::string )专属成员函数
所有函数均直接操作调用者( string 对象),修改后原对象的内容/长度/容量直接变化,无需生成新 string 对象。
- insert :在指定位置插入字符、字符串或迭代器区间内容,直接扩展原字符串长度并修改内容。
- erase :删除指定位置的单个字符,或指定迭代器区间内的字符,直接缩短原字符串长度并修改内容。
- append :在原字符串末尾追加字符、字符串或迭代器区间内容,直接扩展原字符串长度。
- assign :用新内容(字符、字符串、迭代器区间或重复字符)完全替换原字符串内容,原对象直接被覆盖。
- replace :替换原字符串中指定位置/迭代器区间的内容(替换为字符、字符串或迭代器区间),直接修改原对象(长度可能增/减/不变)。
- pop_back :删除原字符串的最后一个字符,直接缩短长度(需确保字符串非空)。
- push_back :在原字符串末尾添加一个字符,直接扩展长度。
- resize :调整原字符串的长度——若新长度大于原长度,用指定字符(默认空字符)补全;若小于原长度,截断末尾字符,直接修改原对象长度和内容。
- swap (成员版):与另一个 string 对象交换内容、长度和容量,两个原对象均被直接修改(无额外内存拷贝,效率高)。
- clear :清空原字符串所有内容,使原对象变为空字符串(长度归 0,容量可能保留)。
二、序列式容器(以 std::vector 、 std::list 、 std::deque 为例)成员函数
容器对象调用这些函数后,自身的元素、大小( size )甚至容量( capacity )直接变化,无需生成新容器对象。
1. 通用操作(多容器支持)
- push_back :在容器末尾添加一个元素( vector / deque / list 均支持),直接增加容器大小; vector 若容量不足会自动扩容,但仍修改原对象。
- pop_back :删除容器末尾的一个元素( vector / deque / list 均支持),直接减小容器大小,不影响原对象其他元素的存储地址( list 无地址概念)。
- erase :删除指定位置的单个元素,或指定迭代器区间内的元素( vector / deque / list 均支持),直接减小容器大小; vector / deque 会移动后续元素填补空位, list 直接调整节点指针。
- insert :在指定位置插入单个元素、多个相同元素或迭代器区间内容( vector / deque / list 均支持),直接增加容器大小; vector 若容量不足会扩容, list 仅需调整节点指针。
- resize :调整容器大小( vector / deque / list 均支持)——新大小大于原大小时,用默认构造或指定的元素补全;新大小小于原大小时,截断末尾元素,直接修改原容器的元素数量。
- clear :清空容器内所有元素( vector / deque / list 均支持),使容器大小归 0; vector 容量可能保留, list 会释放节点内存,但均不改变原容器对象本身。
- swap (成员版):与另一个同类型容器交换所有元素、大小和容量( vector / deque / list 均支持),两个原容器均被直接修改,无元素拷贝(仅交换内部指针/状态)。
2. 特定容器专属操作
- std::list 专属:
- push_front / pop_front :在链表头部添加/删除元素,直接修改原链表大小( vector 不支持)。
- sort (成员函数):直接对原链表元素排序(无需像 vector 那样依赖 std::sort ),排序后原链表结构直接变化。
- reverse (成员函数):反转原链表中元素的顺序,直接修改原链表结构。
- std::deque 专属:
- push_front / pop_front :在双端队列头部添加/删除元素,直接修改原队列大小( vector 不支持)。
三、通用算法( <algorithm> 头文件,需作用于容器迭代器)
这些算法通过迭代器直接操作容器内的元素,修改结果直接体现在原容器中,无需生成新容器。
- std::reverse :反转指定迭代器区间内的元素顺序(支持 vector / string / list 等),原容器/字符串的元素顺序直接变化。
- std::sort :对指定迭代器区间内的元素排序(支持 vector / deque 等随机访问容器, list 需用自身成员 sort ),原容器的元素顺序直接按规则重组。
- std::fill :将指定迭代器区间内的所有元素赋值为同一个指定值(支持所有容器),原容器内对应位置的元素直接被覆盖。
- std::fill_n :从指定起始迭代器开始,连续填充 n 个元素为指定值,直接修改原容器的 n 个元素。
- std::replace :在指定迭代器区间内,将所有等于“旧值”的元素替换为“新值”(支持所有容器),原容器内匹配的元素直接被修改。
- std::replace_if :在指定迭代器区间内,将所有满足“判断函数”的元素替换为“新值”,原容器内符合条件的元素直接被修改。
- std::generate :用指定的“生成函数”(无参数,返回值为元素类型)生成值,填充指定迭代器区间,原容器内对应元素直接被生成的值覆盖。
- std::generate_n :从指定起始迭代器开始,用生成函数生成 n 个值并填充,直接修改原容器的 n 个元素。
- std::rotate :将指定迭代器区间 [first, last) 内的元素,以“中间迭代器 middle ”为分界,循环旋转(例如 [1,2,3,4] 以 3 为分界,旋转后为 [3,4,1,2] ),原容器元素顺序直接变化。
- -std::remove / std::remove_if :在指定迭代器区间内,将等于“指定值”( remove )或满足“判断函数”( remove_if )的元素移到区间末尾,返回指向“新末尾”的迭代器;原容器的大小未变,但前半部分(有效元素)直接被调整,需配合容器的 erase 才能彻底删除末尾无效元素(本质仍是对原容器的修改)。std::unique :在指定迭代器区间内,移除相邻的重复元素(仅保留第一个),将重复元素移到区间末尾,返回新末尾迭代器;原容器大小未变,有效元素部分直接被调整,需配合 erase 彻底删除重复元素(修改作用于原容器)。
核心共性总结
以上所有函数均满足“操作直接作用于原对象,修改结果实时体现在原对象上,无需创建新对象来存储修改后的内容”——无论是字符串的长度变化、容器的元素增减,还是元素值/顺序的调整,最终都无需通过“原对象 → 新对象”的复制过程,原对象本身就是修改的载体。
题8 : 手机
代码:
#include<iostream>
#include<string>
using namespace std;int count[26] = { 1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,4,1,2,3,1,2,3,4 };int main()
{int n = 0;string s;getline(cin, s);for (auto ch : s){if (ch == ' '){n += 1;}else{n += count[ch - 'a'];}}cout << n << endl;return 0;
}
思路:这段代码是为了解决“计算手机九宫格键盘输入字符串的总按键次数”的问题,以下是详细分析:
1. 问题背景(结合题目)
手机九宫格键盘的字符分布如下:
- 2键: abc (按1次出 a ,2次出 b ,3次出 c )
- 3键: def (同理1/2/3次)
- 4键: ghi (1/2/3次)
- 5键: jkl (1/2/3次)
- 6键: mno (1/2/3次)
- 7键: pqrs (1/2/3/4次)
- 8键: tuv (1/2/3次)
- 9键: wxyz (1/2/3/4次)
- 0键:按1次出空格
我们需要统计输入字符串中每个字符(含空格)对应的按键次数,最后求和。
2. 代码逐部分解析
(1) count 数组初始化int count[26] = { 1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,4,1,2,3,1,2,3,4 };
- 数组长度为26,对应26个小写英文字母 a-z 。
- 每个元素的值表示该字母在九宫格键盘上的按键次数。例如:
- count[0] ( a )= 1(2键按1次)
- count[18] ( s )= 4(7键按4次)
- count[25] ( z )= 4(9键按4次)
(2)主函数逻辑int main() {int n = 0; // 总按键次数累加器,初始为0string s; // 存储输入的字符串getline(cin, s); // 读取整行输入(包含空格)for (auto ch : s) // 遍历字符串中的每个字符{if (ch == ' ') // 如果是空格{n += 1; // 空格对应0键按1次,次数+1}else // 如果是字母{// ch - 'a' 计算字母在字母表中的索引(a→0,b→1,…,z→25)// 从count数组中取出对应按键次数,累加到nn += count[ch - 'a'];}}cout << n << endl; // 输出总按键次数return 0; }
综上,这段代码通过预存每个字母的按键次数,遍历输入字符串并累加,高效地解决了手机键盘按键次数统计的问题。
题9 : 口算练习题
代码:
#include<iostream>
#include<string>
using namespace std;
int main()
{int i = 0;cin >> i;string op;//操作符string last;//记录上一次的运算类型while (i--){//输入数据int n1, n2;int r; //计算的结果string ans;//拼接完整算式cin >>op; //均为单个字符 : a b c 275if (op == "a" || op == "b" || op == "c"){cin >> n1 >> n2;ans += to_string(n1);if (op == "a"){r = n1 + n2;ans += "+";ans += to_string(n2);ans += "=";ans += to_string(r);}else if (op == "b"){r = n1 - n2;ans += "-";ans += to_string(n2);ans += "=";ans += to_string(r);}else{r = n1 * n2;ans += "*";ans += to_string(n2);ans += "=";ans += to_string(r);}last = op;}else//这一行有两个数据,执行上一次的运算{n1 = stoi(op);ans += to_string(n1);cin >> n2;if (last == "a"){r = n1 + n2;ans += "+";ans += to_string(n2);ans += "=";ans += to_string(r);}else if (last == "b"){r = n1 - n2;ans += "-";ans += to_string(n2);ans += "=";ans += to_string(r);}else{r = n1 * n2;ans += "*";ans += to_string(n2);ans += "=";ans += to_string(r);}}cout << ans << endl;cout << ans.size() << endl;}return 0;
}
要解决这道“口算练习题”问题,我们可以从需求分析和代码逻辑两方面来拆解推导思路:
推导思路:如何想到这种方法?
- 1. 理解问题本质:题目要求处理两种输入格式的算式——“三个数据(含运算类型)”或“两个数据(继承上一次运算类型)”,并输出完整算式和长度。核心是记录运算类型的状态,并对不同输入格式做分支处理。
- 2. 状态管理需求:由于存在“继承上一次运算类型”的情况,需要一个变量(如 last )来存储上一次的运算类型( a / b / c )。
- 3. 字符串拼接需求:要输出完整算式(如 5+8=13 ),需要将数字、运算符、结果拼接成字符串,因此用 string 类型的 ans 来动态构建算式。
- 4. 分支逻辑设计:根据输入的“运算类型是否存在”(即一行是三个数据还是两个数据),分两种大情况处理,每种情况再根据运算类型( a / b / c )细分小分支。
代码过程逻辑详解:
以下结合代码逐行解析逻辑:
步骤拆解
- 1. 输入总数量 i :先读取需要处理的算式总数 i ,通过 while(i--) 循环处理每个算式。
- 2. 初始化变量: op 存储当前行的第一个输入(可能是运算类型或第一个数), last 存储上一次的运算类型, ans 拼接完整算式, n1 / n2 存储运算数, r 存储结果。
- 3. 分支1:三个数据的情况:
- 若 op 是 "a" / "b" / "c" ,说明当前行有三个数据(运算类型+两个数)。
- 读取 n1 和 n2 ,根据 op 的类型( a 加、 b 减、 c 乘)计算 r ,并拼接成 “n1+/-/*n2=r” 的字符串 ans 。
- 用 last 记录当前 op ,供后续“两个数据”的情况使用。
- 4. 分支2:两个数据的情况:
- 若 op 不是 "a" / "b" / "c" ,说明当前行只有两个数据(继承上一次运算类型)。
- 将 op 转为整数 n1 ,读取 n2 ,根据 last 的类型计算 r ,同样拼接成 “n1+/-/*n2=r” 的字符串 ans 。
- 5. 输出结果:每个算式处理完成后,输出 ans (完整算式)和 ans.size() (算式长度)。
这种方法的核心是通过 last 变量维护运算类型的状态,并利用 string 的拼接能力构建完整算式,从而高效处理两种输入格式的分支逻辑,完全贴合题目的需求。
注意事项:
在 C++ 中, std::string 本质上是对字符数组的封装(内部通过字符数组存储字符串,以 '\0' 结尾)。正因为它是“字符数组”的逻辑,所以:
- 当表示字符串字面量(用于初始化 string 或与 string 比较)时,必须用双引号(如 "a" 、 "abc" )—— 因为双引号表示的是“字符数组”(即使只有一个字符,也是长度为2的数组: 'a' + '\0' )。
- 单引号表示单个字符字面量(如 'a' ),它的类型是 char ,和 string (字符数组的封装)是不同的类型,因此不能直接用于 string 的初始化或比较。
简单来说: string 是“字符数组的封装”,所以表示它的字面量必须用双引号;单引号是单个 char ,和 string 类型不兼容。
函数补充:
to_string 和 stoi 是 C++ 标准库中用于字符串与数值类型转换的关键函数,以下是详细介绍:
to_string 函数
- 功能:将数值类型(如 int 、 double 等)转换为 std::string 类型的字符串。
- 头文件: <string>
- 使用场景:在本题中,用于将运算数( n1 、 n2 )和运算结果( r )转换为字符串,以便拼接成完整的算式(如 “5+8=13” )。
示例:
int num = 123; string s = to_string(num); // s 的值为 "123"
stoi 函数
- 功能:将 std::string 类型的字符串转换为 int 类型的整数。
- 头文件: <string>
- 使用场景:在本题中,当输入行只有两个数据时,第一个数据是字符串形式的数字(如输入 “5” ),需要用 stoi 将其转换为 int 类型的运算数( n1 )。
示例:
string s = "456"; int num = stoi(s); // num 的值为 456
补充:类似的转换函数
C++ 标准库中还有一系列用于字符串与数值转换的函数,覆盖不同数值类型:
- stof :字符串转 float
- stod :字符串转 double
- stol :字符串转 long
- stoll :字符串转 long long
- 反向转换(数值转字符串):除了 to_string ,还可以用字符串流( stringstream )实现更灵活的转换,但 to_string 是最直接的方式。
在本题的代码中,这两个函数的配合使用,完美解决了“数值与字符串拼接”的需求——既可
以将数字转成字符串来构建算式,也可以将输入的字符串数字转成整数来参与运算。
总结:
-
回文判断的三种方法:双指针法(两端向中间遍历)、标记法(全程检查字符)和反转对比法(比较原字符串与反转字符串)。
-
手机按键次数统计:通过预存字母按键次数的数组,累加输入字符串中各字符对应的按键次数(包括空格处理)。
-
口算练习题处理:使用状态变量记录上一次运算类型,处理两种输入格式(三数据和两数据),利用字符串拼接生成完整算式并计算长度。
代码特点:
- 回文判断:强调原地操作和状态维护
- 手机按键:使用数组映射提高效率
- 口算题:结合字符串转换和条件分支处理多种输入情况
文章还补充了C++中字符串与数值转换函数(to_string、stoi)的使用说明。
感谢大家的观看