#LC344. 反转字符串
反转字符串
你好!我是你的OI竞赛教练。今天我们要把LeetCode上经典的第344题《反转字符串》,改编成一道标准的NOI/CSP风格的题目。
这道题看似简单,但它是理解线性表操作、双指针算法以及空间复杂度约束的基石。
题目描述:字符串的镜像仪式 (String Reversal)
【题目背景】 在古老的字符王国中,有一种神秘的镜像仪式。为了解开封印,你需要将一段咒语(字符串)首尾倒置。但是,施法空间非常狭窄,你无法召唤新的卷轴来抄写咒语,必须在原卷轴上直接修改。
【题目描述】 给定一个由 个可打印 ASCII 字符组成的字符串 。请编写一个程序,将其反转。
特别限制:
你必须原地 (In-place) 修改输入数组、使用 的额外空间解决这一问题。
这意味着你不能为反转后的字符串分配新的内存空间(例如,不能定义一个新的 string ans 来存储结果)。
【输入格式】 输入包含一行。 第一行包含一个字符串 。
【输出格式】 输出一行,表示反转后的字符串。
【样例 1】 输入:
hello
输出:
olleh
【样例 2】 输入:
Hannah
输出:
hannaH
【数据范围与提示】
- 对于 的数据,字符串长度 。
- 字符串仅包含 ASCII 可打印字符。
- 提示:所有的修改必须在原字符串上进行。
一、 预备知识点总结
在攻克这道题之前,请确保你已经掌握了以下知识点(如果忘记了,请复习课本对应章节):
- 数组/字符串的下标索引:理解字符串在内存中本质上是字符数组,可以通过下标 到 访问。
- 变量交换 (Swap):如何交换两个变量的值(使用临时变量
temp或 C++ 内置函数std::swap)。 - 双指针 (Two Pointers):一种基础的算法思想,使用两个变量作为“指针”同时遍历数据结构。
- 空间复杂度 (Space Complexity):理解什么是 额外空间(Auxiliary Space),即除输入数据外,程序运行所需的存储空间不随输入规模 增长。
二、 启发式教学:解题思路引导
来,拿出你的草稿纸,我们画图来模拟一下这个过程。不要急着写代码,先想清楚逻辑。
1. 初始状态
假设我们有一个字符串数组 ['h', 'e', 'l', 'l', 'o']。
想象你的左手食指指向第一个字符,右手食指指向最后一个字符。
下标: 0 1 2 3 4
字符: [ h ][ e ][ l ][ l ][ o ]
^ ^
| |
左手(L) 右手(R)
思考题 1:如果我们想反转整个串,h 应该去哪里?o 应该去哪里?
(学生思考:它们应该互换位置。)
2. 第一步操作
好,我们交换左手和右手指向的字符。
下标: 0 1 2 3 4
字符: [ o ][ e ][ l ][ l ][ h ]
^ ^
| |
已交换 已交换
思考题 2:现在两端的字符已经归位了。接下来我们要处理剩下的部分,你的左手和右手应该怎么移动? (学生思考:左手向右移一格,右手向左移一格。)
3. 迭代过程
移动后:
下标: 0 1 2 3 4
字符: [ o ][ e ][ l ][ l ][ h ]
^ ^
| |
L R
再次交换 e 和 l:
下标: 0 1 2 3 4
字符: [ o ][ l ][ l ][ e ][ h ]
再次移动指针,此时 指向下标 2, 指向下标 2。
关键提问:
- 结束条件是什么? 当左手(L) 和 右手(R) 相遇()或者交错()时,是不是就不用换了?
- 如果不停止会怎样? 如果继续移动, 会跑到 的右边,继续交换的话,字符串是不是又被还原回去了?
4. 复杂度分析
请在草稿纸上计算:
- 时间复杂度:我们总共进行了多少次交换?大概是 次。常数忽略不计,所以时间复杂度是 。这符合要求吗?(符合,线性时间)。
- 空间复杂度:我们在交换过程中,除了输入原本的字符串,额外用了多少内存?
- 我们需要一个变量存
L,一个变量存R,可能还需要一个临时变量temp用来交换。 - 不管字符串有一万个字还是一个字,这 3 个变量是固定的。
- 所以额外空间是 。这符合题目“原地修改”的要求吗?(符合)。
- 我们需要一个变量存
三、 读题理解关键词
在NOI类竞赛中,审题至关重要。针对此类题目,请圈出以下关键词:
- “原地” (In-place):这是最核心的约束。看到这个词,立刻要反应过来:禁止
string b = ""; for(...) b += s[i];这种写法。 - “O(1) 额外空间”:与“原地”呼应,强调不能开辟与 相关的辅助数组。
- “反转”:明确操作逻辑,首尾对称交换。
四、 NOI风格 C++14 样例代码
虽然你不能直接照抄,但这里提供一份符合竞赛标准的参考实现。注意代码风格的规范性。
/**
* 题目:字符串的镜像仪式 (String Reversal)
* 知识点:双指针, 模拟, 字符串处理
* 时间复杂度:O(N)
* 空间复杂度:O(1)
*/
#include <iostream>
#include <string>
#include <vector>
#include <algorithm> // 用于 std::swap
// 使用标准命名空间,竞赛中常用,但在工程中需谨慎
using namespace std;
// 核心逻辑函数
// 传入引用 string& s,保证是在原字符串上修改,不产生拷贝
void solve(string& s) {
int left = 0;
int right = s.length() - 1;
// 双指针向中间逼近
// 循环条件:只要左指针在右指针左侧,就继续交换
while (left < right) {
// 交换左右指针指向的字符
// 也可以手写: char temp = s[left]; s[left] = s[right]; s[right] = temp;
swap(s[left], s[right]);
// 移动指针
left++;
right--;
}
}
int main() {
// 优化 C++ 标准流 I/O 性能,防止在大数据量下超时
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
// 题目输入是一行字符串。
// 如果字符串中间没有空格,用 cin >> s 即可。
// 既然原题LeetCode的输入是字符数组(看作整体),且通常可能包含空格,
// 在NOI中如果包含空格通常用 getline(cin, s)。
// 这里为了适配最通用的情况,假设输入可能含空格:
if (getline(cin, s)) {
solve(s);
cout << s << endl;
}
return 0;
}
五、 课后思考(针对学有余力的同学)
- 递归写法:你能用递归实现这个功能吗?
- 提示:递归会消耗栈空间。如果字符串长度是 ,递归层数也是 ,这时的空间复杂度还是 吗?(答案:不是,是 的栈空间,可能会导致 Stack Overflow,所以本题虽可用递归逻辑,但不符合 空间限制,除非编译器进行了尾递归优化)。
- 位运算交换:如果不允许使用临时变量
temp,你能用异或 (^) 运算交换两个字符吗?- 提示:
a = a ^ b; b = a ^ b; a = a ^ b;。但这在现代C++开发中并不推荐,因为编译器优化已经足够好,且易读性差,仅作思维拓展。
- 提示: