title: Reverse String
slug: reverse-string
difficulty: easy
leetcode_id: 344
leetcode_url: https://leetcode.com/problems/reverse-string/
categories:
  - strings
  - two-pointers
patterns:
  - two-pointers

description: |
  Write a function that reverses a string. The input string is given as an array of characters `s`.

  You must do this by modifying the input array *in-place* with `O(1)` extra memory.

constraints: |
  - `1 <= s.length <= 10^5`
  - `s[i]` is a printable ASCII character

examples:
  - input: 's = ["h","e","l","l","o"]'
    output: '["o","l","l","e","h"]'
    explanation: "The array is reversed in place."
  - input: 's = ["H","a","n","n","a","h"]'
    output: '["h","a","n","n","a","H"]'
    explanation: "The array is reversed in place. Notice how the case of each character is preserved."

explanation:
  intuition: |
    Imagine holding a deck of cards face up in your hands. To reverse the order, you could pick up the top card and the bottom card, swap them, then move inward and repeat. Eventually, your hands meet in the middle and the deck is reversed.

    This is exactly the **two-pointer technique**: place one pointer at the start and one at the end, swap the elements they point to, then move both pointers toward the center. When the pointers meet or cross, you're done.

    The key insight is that reversing is just a series of **symmetric swaps**. The first element swaps with the last, the second with the second-to-last, and so on. Each swap is independent, so we only need `O(1)` extra space for the temporary variable during each swap.

  approach: |
    We solve this using the **Two-Pointer Approach**:

    **Step 1: Initialise two pointers**

    - `left`: Set to `0`, pointing to the first element
    - `right`: Set to `len(s) - 1`, pointing to the last element

    &nbsp;

    **Step 2: Swap and move inward**

    - While `left < right`:
      - Swap `s[left]` and `s[right]`
      - Increment `left` by 1
      - Decrement `right` by 1

    &nbsp;

    **Step 3: Termination**

    - The loop exits when `left >= right`, meaning all swaps are complete
    - The array is now reversed in place

    &nbsp;

    This approach works because each swap puts two elements in their final positions. After `n/2` swaps (where `n` is the array length), every element has been moved to its mirror position.

  common_pitfalls:
    - title: Creating a New Array
      description: |
        A common mistake is to create a new reversed array and copy it back:

        ```python
        s[:] = s[::-1]  # Creates a copy, uses O(n) space
        ```

        While this works functionally, it violates the `O(1)` space constraint. The problem explicitly requires in-place modification without extra memory proportional to the input size.
      wrong_approach: "Creating a reversed copy"
      correct_approach: "Swap elements in place with two pointers"

    - title: Off-by-One Errors
      description: |
        When setting up the pointers or the loop condition, it's easy to make off-by-one mistakes:

        - Using `right = len(s)` instead of `right = len(s) - 1` causes an index out of bounds error
        - Using `left <= right` instead of `left < right` causes an unnecessary swap when pointers meet at the middle element (harmless but wasteful)

        Always trace through a small example like `["a", "b", "c"]` to verify your indices.
      wrong_approach: "Incorrect index initialization or loop bounds"
      correct_approach: "Carefully set right = len(s) - 1 and use left < right"

    - title: Forgetting Python's Swap Syntax
      description: |
        In Python, you can swap two variables without a temporary variable using tuple unpacking:

        ```python
        s[left], s[right] = s[right], s[left]
        ```

        Some developers new to Python write more verbose swap logic with a temp variable, which works but is unnecessary. Both approaches use `O(1)` space.

  key_takeaways:
    - "**Two-pointer pattern**: When operating on both ends of a linear structure, two pointers moving inward is often the key"
    - "**In-place modification**: Swapping elements directly avoids extra memory allocation"
    - "**Symmetric operations**: Reversing is a series of independent swaps, making it parallelizable and simple to implement"
    - "**Foundation for harder problems**: This technique extends to problems like reversing words in a string, palindrome checking, and rotating arrays"

  time_complexity: "O(n). We perform `n/2` swaps, visiting each element exactly once."
  space_complexity: "O(1). We only use two pointer variables regardless of input size."

solutions:
  - approach_name: Two Pointers
    is_optimal: true
    code: |
      def reverse_string(s: list[str]) -> None:
          # Start pointers at both ends
          left, right = 0, len(s) - 1

          # Swap until pointers meet in the middle
          while left < right:
              # Swap the characters at left and right positions
              s[left], s[right] = s[right], s[left]
              # Move pointers toward the center
              left += 1
              right -= 1
    explanation: |
      **Time Complexity:** O(n) — We iterate through half the array, performing one swap per iteration.

      **Space Complexity:** O(1) — Only two integer variables are used.

      The two-pointer technique efficiently reverses the array in a single pass from both ends. Each swap places two elements in their final positions, completing the reversal in `n/2` iterations.

  - approach_name: Recursion
    is_optimal: false
    code: |
      def reverse_string(s: list[str]) -> None:
          def helper(left: int, right: int) -> None:
              # Base case: pointers have met or crossed
              if left >= right:
                  return
              # Swap current pair
              s[left], s[right] = s[right], s[left]
              # Recurse on the inner portion
              helper(left + 1, right - 1)

          helper(0, len(s) - 1)
    explanation: |
      **Time Complexity:** O(n) — Same number of swaps as the iterative approach.

      **Space Complexity:** O(n) — The call stack grows to depth `n/2`, using O(n) space.

      While this recursive solution demonstrates the same logic, it violates the `O(1)` space constraint due to the call stack. It's included to show how recursion maps to the iterative solution, but the iterative approach is preferred here.
