title: Single Number
slug: single-number
difficulty: easy
leetcode_id: 136
leetcode_url: https://leetcode.com/problems/single-number/
categories:
  - arrays
  - math
patterns:
  - slug: bit-manipulation
    is_optimal: true

function_signature: "def single_number(nums: list[int]) -> int:"

test_cases:
  visible:
    - input: { nums: [2, 2, 1] }
      expected: 1
    - input: { nums: [4, 1, 2, 1, 2] }
      expected: 4
    - input: { nums: [1] }
      expected: 1
  hidden:
    - input: { nums: [5, 3, 5] }
      expected: 3
    - input: { nums: [-1, -1, -2] }
      expected: -2
    - input: { nums: [0, 1, 0] }
      expected: 1
    - input: { nums: [1, 2, 3, 2, 1] }
      expected: 3
    - input: { nums: [7, 7, 8, 8, 9] }
      expected: 9
    - input: { nums: [100] }
      expected: 100

description: |
  Given a **non-empty** array of integers `nums`, every element appears *twice* except for one. Find that single one.

  You must implement a solution with a linear runtime complexity and use only constant extra space.

constraints: |
  - `1 <= nums.length <= 3 * 10^4`
  - `-3 * 10^4 <= nums[i] <= 3 * 10^4`
  - Each element in the array appears twice except for one element which appears only once.

examples:
  - input: "nums = [2,2,1]"
    output: "1"
    explanation: "The element 1 appears only once, while 2 appears twice."
  - input: "nums = [4,1,2,1,2]"
    output: "4"
    explanation: "The element 4 appears only once, while 1 and 2 each appear twice."
  - input: "nums = [1]"
    output: "1"
    explanation: "There is only one element, so it must be the single number."

explanation:
  intuition: |
    At first glance, you might think of using a hash set or hash map to count occurrences. But the problem explicitly requires **constant space**, ruling out data structures that grow with input size.

    The key insight lies in a special property of the **XOR (exclusive or)** operation:

    - `a XOR a = 0` — any number XORed with itself equals zero
    - `a XOR 0 = a` — any number XORed with zero equals itself
    - XOR is **commutative** and **associative** — the order doesn't matter

    Think of it like this: imagine each number is a light switch. When you flip a switch twice (the number appears twice), it returns to its original position (cancels out to zero). When you flip a switch only once (the single number), it stays flipped.

    If we XOR all numbers together, every pair will cancel out (`a XOR a = 0`), leaving only the single number that has no pair to cancel with.

  approach: |
    We solve this using **XOR Bit Manipulation**:

    **Step 1: Initialise the result**

    - `result`: Set to `0` since `a XOR 0 = a` (XORing with zero doesn't change a value)

    &nbsp;

    **Step 2: Iterate through all numbers**

    - For each number in the array, XOR it with `result`
    - Pairs of identical numbers will cancel each other out: `result XOR a XOR a = result`
    - The single number will remain: `0 XOR single = single`

    &nbsp;

    **Step 3: Return the result**

    - After XORing all elements, `result` contains only the single number
    - All pairs have cancelled to zero, leaving just the unpaired element

    &nbsp;

    This works because XOR is both commutative (order doesn't matter) and associative (grouping doesn't matter), so the pairs will always find each other and cancel out regardless of their positions in the array.

  common_pitfalls:
    - title: Using Hash Map for Counting
      description: |
        A natural instinct is to use a hash map to count occurrences of each number, then find the one with count 1.

        While this works correctly with O(n) time complexity, it uses **O(n) space** to store the counts. The problem explicitly requires O(1) space, so this approach violates the constraints.

        For interviews, always read the space complexity requirement carefully before choosing your approach.
      wrong_approach: "Hash map to count occurrences"
      correct_approach: "XOR all elements together"

    - title: Using Math with Sum
      description: |
        Another approach: find all unique numbers, sum them and multiply by 2, then subtract the original sum. The difference is the single number.

        For example, with `[4,1,2,1,2]`: unique sum = `4+1+2 = 7`, so `2*7 - 10 = 4`.

        However, this requires storing unique elements (O(n) space) or sorting (O(n log n) time), neither of which meets the constraints. It can also cause integer overflow with large numbers.
      wrong_approach: "2 * sum(set) - sum(array)"
      correct_approach: "XOR all elements together"

    - title: Not Understanding XOR Properties
      description: |
        If you're unfamiliar with XOR, you might not realise that `a XOR a = 0` and `a XOR 0 = a`. These properties are essential to understand why the solution works.

        XOR returns 1 when bits are different, 0 when same. So any number XORed with itself has all bits become 0.

        Practice XOR operations: `5 XOR 5 = 0`, `5 XOR 0 = 5`, `5 XOR 3 XOR 5 = 3`.

  key_takeaways:
    - "**XOR for pair cancellation**: When elements appear in pairs, XORing all elements leaves only the unpaired one"
    - "**Bit manipulation for O(1) space**: XOR operates on the number itself without extra storage, meeting strict space constraints"
    - "**Know your XOR properties**: `a XOR a = 0`, `a XOR 0 = a`, and XOR is commutative and associative"
    - "**Foundation for harder problems**: This pattern extends to problems like finding two single numbers (using XOR with bit partitioning)"

  time_complexity: "O(n). We traverse the array exactly once, performing a constant-time XOR operation for each element."
  space_complexity: "O(1). We only use a single variable (`result`) regardless of the input size."

solutions:
  - approach_name: XOR Bit Manipulation
    is_optimal: true
    code: |
      def single_number(nums: list[int]) -> int:
          # Start with 0 since a XOR 0 = a
          result = 0

          for num in nums:
              # XOR each number with result
              # Pairs cancel out: a XOR a = 0
              # Single number remains: 0 XOR single = single
              result ^= num

          return result
    explanation: |
      **Time Complexity:** O(n) — Single pass through the array.

      **Space Complexity:** O(1) — Only one variable used.

      We XOR all numbers together. Since `a XOR a = 0` and `a XOR 0 = a`, all pairs cancel out, leaving only the single number. This is the most elegant solution that meets both time and space requirements.

  - approach_name: Hash Set
    is_optimal: false
    code: |
      def single_number(nums: list[int]) -> int:
          seen = set()

          for num in nums:
              if num in seen:
                  # Second occurrence: remove from set
                  seen.remove(num)
              else:
                  # First occurrence: add to set
                  seen.add(num)

          # The only remaining element is the single number
          return seen.pop()
    explanation: |
      **Time Complexity:** O(n) — Single pass with O(1) set operations.

      **Space Complexity:** O(n) — Set can grow up to n/2 elements.

      This approach adds numbers to a set on first occurrence and removes them on second occurrence. The single number stays in the set. While correct, it violates the O(1) space constraint and would not be accepted in an interview requiring constant space.

  - approach_name: Hash Map Counting
    is_optimal: false
    code: |
      from collections import Counter

      def single_number(nums: list[int]) -> int:
          # Count occurrences of each number
          counts = Counter(nums)

          # Find the number with count 1
          for num, count in counts.items():
              if count == 1:
                  return num
    explanation: |
      **Time Complexity:** O(n) — One pass to count, one pass to find.

      **Space Complexity:** O(n) — Hash map stores all unique elements.

      This is the most intuitive approach: count each number and find the one appearing once. While easy to understand, it uses O(n) space and doesn't meet the problem's constant space requirement. Included to show the progression from intuitive to optimal solutions.
