#LC344. 反转字符串

反转字符串

你好!我是你的OI竞赛教练。今天我们要把LeetCode上经典的第344题《反转字符串》,改编成一道标准的NOI/CSP风格的题目。

这道题看似简单,但它是理解线性表操作双指针算法以及空间复杂度约束的基石。


题目描述:字符串的镜像仪式 (String Reversal)

【题目背景】 在古老的字符王国中,有一种神秘的镜像仪式。为了解开封印,你需要将一段咒语(字符串)首尾倒置。但是,施法空间非常狭窄,你无法召唤新的卷轴来抄写咒语,必须在原卷轴上直接修改。

【题目描述】 给定一个由 NN 个可打印 ASCII 字符组成的字符串 SS。请编写一个程序,将其反转。

特别限制: 你必须原地 (In-place) 修改输入数组、使用 O(1)O(1) 的额外空间解决这一问题。 这意味着你不能为反转后的字符串分配新的内存空间(例如,不能定义一个新的 string ans 来存储结果)。

【输入格式】 输入包含一行。 第一行包含一个字符串 SS

【输出格式】 输出一行,表示反转后的字符串。

【样例 1】 输入:

hello

输出:

olleh

【样例 2】 输入:

Hannah

输出:

hannaH

【数据范围与提示】

  • 对于 100%100\% 的数据,字符串长度 1S1051 \le |S| \le 10^5
  • 字符串仅包含 ASCII 可打印字符。
  • 提示:所有的修改必须在原字符串上进行。

一、 预备知识点总结

在攻克这道题之前,请确保你已经掌握了以下知识点(如果忘记了,请复习课本对应章节):

  1. 数组/字符串的下标索引:理解字符串在内存中本质上是字符数组,可以通过下标 00N1N-1 访问。
  2. 变量交换 (Swap):如何交换两个变量的值(使用临时变量 temp 或 C++ 内置函数 std::swap)。
  3. 双指针 (Two Pointers):一种基础的算法思想,使用两个变量作为“指针”同时遍历数据结构。
  4. 空间复杂度 (Space Complexity):理解什么是 O(1)O(1) 额外空间(Auxiliary Space),即除输入数据外,程序运行所需的存储空间不随输入规模 NN 增长。

二、 启发式教学:解题思路引导

来,拿出你的草稿纸,我们画图来模拟一下这个过程。不要急着写代码,先想清楚逻辑。

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

再次交换 el

下标:   0    1    2    3    4
字符: [ o ][ l ][ l ][ e ][ h ]

再次移动指针,此时 LL 指向下标 2,RR 指向下标 2。

关键提问

  1. 结束条件是什么? 当左手(L) 和 右手(R) 相遇(L=RL = R)或者交错(L>RL > R)时,是不是就不用换了?
  2. 如果不停止会怎样? 如果继续移动,LL 会跑到 RR 的右边,继续交换的话,字符串是不是又被还原回去了?

4. 复杂度分析

请在草稿纸上计算:

  • 时间复杂度:我们总共进行了多少次交换?大概是 N/2N/2 次。常数忽略不计,所以时间复杂度是 O(N)O(N)。这符合要求吗?(符合,线性时间)。
  • 空间复杂度:我们在交换过程中,除了输入原本的字符串,额外用了多少内存?
    • 我们需要一个变量存 L,一个变量存 R,可能还需要一个临时变量 temp 用来交换。
    • 不管字符串有一万个字还是一个字,这 3 个变量是固定的。
    • 所以额外空间是 O(1)O(1)。这符合题目“原地修改”的要求吗?(符合)。

三、 读题理解关键词

在NOI类竞赛中,审题至关重要。针对此类题目,请圈出以下关键词:

  1. “原地” (In-place):这是最核心的约束。看到这个词,立刻要反应过来:禁止 string b = ""; for(...) b += s[i]; 这种写法。
  2. “O(1) 额外空间”:与“原地”呼应,强调不能开辟与 NN 相关的辅助数组。
  3. “反转”:明确操作逻辑,首尾对称交换。

四、 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;
}

五、 课后思考(针对学有余力的同学)

  1. 递归写法:你能用递归实现这个功能吗?
    • 提示:递归会消耗栈空间。如果字符串长度是 10510^5,递归层数也是 10510^5,这时的空间复杂度还是 O(1)O(1) 吗?(答案:不是,是 O(N)O(N) 的栈空间,可能会导致 Stack Overflow,所以本题虽可用递归逻辑,但不符合 O(1)O(1) 空间限制,除非编译器进行了尾递归优化)。
  2. 位运算交换:如果不允许使用临时变量 temp,你能用异或 (^) 运算交换两个字符吗?
    • 提示a = a ^ b; b = a ^ b; a = a ^ b;。但这在现代C++开发中并不推荐,因为编译器优化已经足够好,且易读性差,仅作思维拓展。