name: Cyclic Sort
slug: cyclic-sort
difficulty_level: 2
pattern_type: algorithm
display_order: 21

description: >
  Place each number at its "correct" index when dealing with arrays containing
  numbers in the range [1, n] or [0, n-1].

when_to_use: |
  - Array contains numbers from 1 to n (or 0 to n-1)
  - Finding missing/duplicate numbers in such arrays
  - Problems asking for O(1) space and O(n) time
  - "First missing positive" type problems

metaphor: |
  Imagine a row of n chairs numbered 1 to n, and n people each holding a
  ticket with their seat number. Instead of searching for each person's seat,
  you ask everyone to swap positions until they're in their ticketed seat.
  After at most n swaps, everyone is seated correctly.

core_concept: |
  For an array where values should map directly to indices (e.g., value 3
  belongs at index 2 if 1-indexed), repeatedly swap each element to its
  correct position until the array is "sorted."

  Key insight: Each swap places at least one element correctly, so at most
  n swaps are needed -> O(n) time with O(1) space.

code_template: |
  def cyclic_sort(nums: list[int]) -> list[int]:
      """Sort array where values are in range [1, n]."""
      i = 0
      while i < len(nums):
          # Correct index for value nums[i] (1-indexed value -> 0-indexed position)
          correct_idx = nums[i] - 1

          # If not in correct position and not a duplicate, swap
          if nums[i] != nums[correct_idx]:
              nums[i], nums[correct_idx] = nums[correct_idx], nums[i]
          else:
              i += 1
      return nums

  def find_missing(nums: list[int]) -> int:
      """Find missing number after cyclic sort."""
      cyclic_sort(nums)
      for i, num in enumerate(nums):
          if num != i + 1:
              return i + 1
      return len(nums) + 1

recognition_signals:
  - "array of length n with values 1 to n"
  - "find the missing number"
  - "find the duplicate"
  - "first missing positive"
  - "O(1) extra space"
  - "in-place rearrangement"

common_mistakes:
  - title: Infinite Loop on Duplicates
    description: |
      Swapping endlessly when current value equals value at target index
      (both are duplicates).
    fix: |
      Check `nums[i] != nums[correct_idx]` before swapping, not just
      `nums[i] != correct_idx + 1`.

  - title: Wrong Index Calculation
    description: |
      Confusing 0-indexed vs 1-indexed. If values are 1 to n, correct
      index is `value - 1`. If values are 0 to n-1, correct index equals value.
    fix: |
      Clearly define the mapping: `correct_idx = nums[i] - 1` for 1-indexed values.

variations:
  - name: Find Missing Number
    description: After cyclic sort, scan for index where value != index + 1
    example: "missing-number"
  - name: Find Duplicate
    description: After cyclic sort, find where value != expected
    example: "find-the-duplicate-number"
  - name: First Missing Positive
    description: Ignore negatives and values > n, then cyclic sort
    example: "first-missing-positive"
  - name: Find All Missing Numbers
    description: Collect all indices where value != expected
    example: "find-all-numbers-disappeared-in-an-array"

related_patterns:
  - counting-sort
  - fast-slow-pointers

prerequisite_patterns: []
