title: Check If N and Its Double Exist
slug: check-if-n-and-its-double-exist
difficulty: easy
leetcode_id: 1346
leetcode_url: https://leetcode.com/problems/check-if-n-and-its-double-exist/
categories:
  - arrays
  - hash-tables
patterns:
  - two-pointers

description: |
  Given an array `arr` of integers, check if there exist two indices `i` and `j` such that:

  - `i != j`
  - `0 <= i, j < arr.length`
  - `arr[i] == 2 * arr[j]`

  Return `true` if such indices exist, otherwise return `false`.

constraints: |
  - `2 <= arr.length <= 500`
  - `-10^3 <= arr[i] <= 10^3`

examples:
  - input: "arr = [10,2,5,3]"
    output: "true"
    explanation: "For i = 0 and j = 2, arr[i] == 10 == 2 * 5 == 2 * arr[j]."
  - input: "arr = [3,1,7,11]"
    output: "false"
    explanation: "There is no i and j that satisfy the conditions."
  - input: "arr = [7,1,14,11]"
    output: "true"
    explanation: "For i = 2 and j = 0, arr[i] == 14 == 2 * 7 == 2 * arr[j]."

explanation:
  intuition: |
    Think of this problem as a **search problem with a twist**. For each number in the array, we're looking for a specific companion: either its double (twice its value) or its half (if the number is even).

    Imagine you're at a party checking name tags. For each person you meet, you're looking for someone whose name tag shows exactly twice the number on yours, or someone whose number is exactly half of yours. You need to find just one such matching pair.

    The key insight is that we can use a **hash set as a "memory"** of numbers we've already seen. As we iterate through the array, for each number `n`, we check: "Have I already seen `2*n`?" or "Have I already seen `n/2`?". If yes, we've found our pair. If not, we add the current number to our set and continue.

    This "remember what you've seen" pattern is extremely common and allows us to avoid checking every pair explicitly.

  approach: |
    We solve this using a **Hash Set Approach**:

    **Step 1: Create an empty hash set**

    - `seen`: A set to store numbers we've encountered so far

    &nbsp;

    **Step 2: Iterate through the array**

    - For each number `n` in the array:
      - Check if `2 * n` exists in `seen` — this means we've already seen a number that is half of `n`'s double
      - Check if `n` is even AND `n // 2` exists in `seen` — this means we've already seen a number that `n` is double of
      - If either condition is true, return `true` immediately
      - Otherwise, add `n` to `seen` and continue

    &nbsp;

    **Step 3: Return the result**

    - If we finish iterating without finding a pair, return `false`

    &nbsp;

    The hash set gives us O(1) lookup time, making the overall solution efficient. Note that we must check if `n` is even before checking `n // 2` to handle odd numbers correctly (since an odd number divided by 2 wouldn't give us a valid integer match).

  common_pitfalls:
    - title: Forgetting the Zero Edge Case
      description: |
        Zero is a special case because `0 == 2 * 0`. If the array contains multiple zeros, the answer should be `true`.

        For example, with `arr = [0, 0]`, we have `arr[0] == 2 * arr[1]` since `0 == 2 * 0`.

        The hash set approach handles this naturally: when we see the second `0`, we check if `2 * 0 = 0` is in the set, and it is (we added the first `0`).
      wrong_approach: "Not considering that zero satisfies the condition with itself"
      correct_approach: "Hash set naturally handles this since 0 is added before the second 0 is checked"

    - title: Integer Division for Odd Numbers
      description: |
        When checking if `n / 2` exists in the set, we must ensure `n` is even first. For odd numbers like `7`, checking `7 // 2 = 3` would give a false positive if `3` happens to be in the set.

        For example, with `arr = [3, 7]`, we don't want to match `7` with `3` just because `7 // 2 = 3`. We need `14` to match with `7`, not `3`.
      wrong_approach: "Checking n // 2 without verifying n is even"
      correct_approach: "Only check n // 2 when n % 2 == 0"

    - title: Using the Same Element Twice
      description: |
        The condition requires `i != j`, meaning we can't use the same element as both the number and its double. This is why we check if the double/half exists in `seen` (elements we've **already** passed) rather than checking the current element against itself.

        The iteration order naturally prevents this: we only check against previously seen elements.
      wrong_approach: "Checking if n == 2 * n (which is only true for n = 0)"
      correct_approach: "Check against previously seen elements stored in the set"

  key_takeaways:
    - "**Hash Set for O(1) Lookup**: When searching for specific values or complements, a hash set transforms O(n) searches into O(1) lookups"
    - "**Two-Way Relationship**: Since `a == 2 * b` implies `b == a / 2`, we can check both directions as we iterate"
    - "**Remember What You've Seen**: This pattern of maintaining a set of seen values is foundational for many array problems (Two Sum, finding duplicates, etc.)"
    - "**Edge Cases Matter**: Zero and negative numbers can create subtle bugs — always consider how your conditions behave with boundary values"

  time_complexity: "O(n). We iterate through the array once, and each hash set operation (lookup and insert) takes O(1) average time."
  space_complexity: "O(n). In the worst case, we store all `n` elements in the hash set before finding a match (or not finding one)."

solutions:
  - approach_name: Hash Set
    is_optimal: true
    code: |
      def check_if_exist(arr: list[int]) -> bool:
          # Set to remember numbers we've seen
          seen = set()

          for n in arr:
              # Check if double of n was seen before
              if 2 * n in seen:
                  return True
              # Check if half of n was seen (only if n is even)
              if n % 2 == 0 and n // 2 in seen:
                  return True
              # Add current number to our memory
              seen.add(n)

          # No valid pair found
          return False
    explanation: |
      **Time Complexity:** O(n) — Single pass through the array with O(1) set operations.

      **Space Complexity:** O(n) — Hash set stores up to n elements.

      For each element, we check if its double or half (when even) has been seen before. The hash set provides constant-time lookups, making this approach efficient.

  - approach_name: Brute Force
    is_optimal: false
    code: |
      def check_if_exist(arr: list[int]) -> bool:
          n = len(arr)

          # Check every pair of elements
          for i in range(n):
              for j in range(n):
                  # Skip same index
                  if i == j:
                      continue
                  # Check if arr[i] is double of arr[j]
                  if arr[i] == 2 * arr[j]:
                      return True

          return False
    explanation: |
      **Time Complexity:** O(n^2) — Nested loops check all pairs.

      **Space Complexity:** O(1) — No extra space used.

      This approach explicitly checks every pair of indices. While correct and easy to understand, it's less efficient than the hash set approach. For the given constraints (`n <= 500`), this would still pass, but the hash set solution is preferred for its scalability.

  - approach_name: Sorting with Binary Search
    is_optimal: false
    code: |
      def check_if_exist(arr: list[int]) -> bool:
          arr.sort()
          n = len(arr)

          for i in range(n):
              target = 2 * arr[i]
              # Binary search for the target
              left, right = 0, n - 1

              while left <= right:
                  mid = (left + right) // 2
                  if arr[mid] == target and mid != i:
                      return True
                  elif arr[mid] < target:
                      left = mid + 1
                  else:
                      right = mid - 1

          return False
    explanation: |
      **Time Complexity:** O(n log n) — Sorting takes O(n log n), then n binary searches each take O(log n).

      **Space Complexity:** O(1) or O(n) — Depends on the sorting algorithm used.

      This approach sorts the array first, then uses binary search to find the double of each element. While more efficient than brute force, it's slightly slower than the hash set approach and modifies the original array (or requires extra space to avoid modification).
