题解 P1553 【数字反转(升级版)】
如果你对 STL 了解得比较透彻,那么这篇题解一定是最简单的(虽然有点长,但思路很清晰)。如果你不是很懂 STL,借此机会了解、学习一下也无妨。 主要思路是,读如字符串后,判断字符串中是属于哪一种类型的数字。一共有四种情况:正整数、正实数、正分数、正百分数。
- 如果是正整数,先反转,再去掉前导零,输出。
- 如果是百分数,先按正整数方案做,最后输出一个百分号。
- 如果是分数或者实数,以分数线 / 小数点为分界,将字符串分为左右两部分,分别进行反转。
综上,我们有以下几个问题需要解决:
- 如何反转?用 STL 中的
std::reverse()。 - 如何去掉前导零?先遍历一遍字符串,看看有多少前导零,然后
std::string::erase()即可。 - 如何提取出子字符串?
std::string::substr()。
对于以上三个函数不是很懂?没关系,文末会有具体解释。 还有两点需要注意,分别是:
- 但要注意,如果数字本身就是零,按照上面的做法会得到空串。所以需要特判。
- 还要注意,小数点之后的部分还需要去掉“后导零”。
代码如下:
//【P1553】数字反转(升级版) - 洛谷 - 100
#include <string>
#include <iostream>
#include <algorithm>
// 自己写的反转函数,返回反转并去掉前导零之后的字符串
std::string reverse(std::string s) {
int zeroCount = 0;
std::reverse(s.begin(), s.end()); // 反转
// 范围 for 循环,用于统计前导零个数
for (auto i : s)
if (i == 48) ++zeroCount;
else break;
s.erase(s.begin(), s.begin() + zeroCount);
return (s != "" ? s : "0"); // 特判
}
// 用于去掉后导零
std::string deleteTail(std::string s) {
int zeroCount = 0;
for (int i = s.size() - 1; i >= 0; --i)
if (s[i] == 48) ++zeroCount;
else break;
s.erase(s.end() - zeroCount, s.end());
return (s != "" ? s : "0");
}
int main() {
std::string s;
std::cin >> s;
if (s.back() == '%') {
std::cout << reverse(s.substr(0, s.size() - 1)) << "%" << std::endl;
return 0;
}
for (auto i : s) {
std::string left, right;
// 其实还有一种不需要遍历字符串的做法,直接 find() 即可,但是当时没想到
if (i == '/') {
left = s.substr(0, s.find("/"));
right = s.substr(s.find("/") + 1);
std::cout << reverse(left) << "/" << reverse(right) << std::endl;
return 0;
}
if (i == '.') {
left = s.substr(0, s.find("."));
right = s.substr(s.find(".") + 1);
std::cout << reverse(left) << "." << deleteTail(reverse(right)) << std::endl;
return 0;
}
}
// 最后剩下的一种情况是正整数
std::cout << reverse(s) << std::endl;
return 0;
}
分别解释一下用到的 STL 算法。
std::reverse(),顾名思义,用于反转序列。需要提供首尾迭代器作为参数。std::string::erase(),传入两个迭代器 l,r,清除[l,r)范围内的字符。std::string::substr(),用于提取子字符串,用法与前者类似。std::string::find(),用来查找字串在母串中第一次出现的位置。
如果还是不太懂,不要着急,初学 STL 总要花费些时间的。与快排等算法结合学习,会起到事半功倍的效果。另外,cppreference.com 这个网站非常有用。如果有不理解的,可以随时去查。