LeetCode 1346 - Check If N and Its Double Exist
The problem gives an integer array arr and asks whether there exist two different indices i and j such that: In simpler
Difficulty: 🟢 Easy
Topics: Array, Hash Table, Two Pointers, Binary Search, Sorting
Solution
Problem Understanding
The problem gives an integer array arr and asks whether there exist two different indices i and j such that:
arr[i] == 2 * arr[j]
In simpler terms, we need to determine whether one number in the array is exactly double another number in the same array.
The indices must be different, meaning we cannot use the same element twice unless it appears multiple times in the array. For example, if the array contains two zeroes, then one zero can be considered the double of the other because:
0 == 2 * 0
The input is an array of integers, which may include:
- Positive numbers
- Negative numbers
- Zero
The expected output is a boolean:
trueif such a pair existsfalseotherwise
The constraints are relatively small:
2 <= arr.length <= 500
-1000 <= arr[i] <= 1000
Since the array size is at most 500, even an O(n^2) brute-force solution is acceptable in practice. However, the problem is designed to encourage recognizing a more efficient lookup-based approach using a hash set.
Several edge cases are important:
- Arrays containing zeroes, especially multiple zeroes
- Negative numbers, because doubling negative values is still valid
- Duplicate values
- Very small arrays of size 2
- Cases where the double appears before the original value
A naive implementation can easily mishandle zero or assume the smaller value always appears first.
Approaches
Brute Force Approach
The most direct solution is to compare every pair of indices (i, j) where i != j.
For each pair, we check:
arr[i] == 2 * arr[j]
If we ever find a valid pair, we immediately return true. If all pairs are checked and none satisfy the condition, we return false.
This approach is correct because it exhaustively examines every possible combination of two different elements. Since the problem only asks whether at least one valid pair exists, a complete search guarantees correctness.
However, this method requires nested loops, resulting in quadratic time complexity. While the constraints are small enough that this still works, it is not the most efficient approach.
Optimal Hash Set Approach
The key observation is that for each number x, we only need to know whether:
2 * xalready existsx / 2already exists, whenxis even
A hash set allows constant-time average lookup operations.
As we iterate through the array, we store previously seen numbers in a set. For each current number:
- If its double already exists in the set, we found a valid pair
- If it is even and its half already exists in the set, we also found a valid pair
This works because the relationship can appear in either order inside the array. The larger value may appear before the smaller value, or vice versa.
Using a hash set reduces the time complexity from O(n^2) to O(n) on average.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Compare every pair of elements |
| Optimal | O(n) | O(n) | Uses a hash set for constant-time lookups |
Algorithm Walkthrough
- Create an empty hash set called
seen.
The set will store numbers we have already processed. A hash set is chosen because it provides average O(1) lookup time.
2. Iterate through each number num in the array.
We process elements one at a time and check whether a matching value has already appeared earlier.
3. Check whether num * 2 exists in the set.
If it does, then we previously encountered a number whose double is the current value relationship.
4. Check whether num is even and whether num // 2 exists in the set.
This handles the reverse ordering case where the smaller number appeared earlier. We only check halves for even numbers because doubling integers always produces even results.
5. If either condition is true, return True.
We have found two distinct elements satisfying the condition. 6. Add the current number to the set.
Future elements can now compare against this value.
7. If the loop finishes without finding a valid pair, return False.
Why it works
The algorithm maintains the invariant that seen contains all previously processed numbers.
For every current number x, a valid pair exists if either:
2 * x
has already appeared, or:
x / 2
has already appeared when x is even.
Because every element is checked exactly once against all previously seen values, every valid pair is eventually detected. Therefore, the algorithm always produces the correct answer.
Python Solution
from typing import List
class Solution:
def checkIfExist(self, arr: List[int]) -> bool:
seen = set()
for num in arr:
if (num * 2) in seen:
return True
if num % 2 == 0 and (num // 2) in seen:
return True
seen.add(num)
return False
The implementation begins by creating an empty set called seen. This set stores all previously processed numbers.
The loop iterates through each element in the array exactly once. For every number, the code performs two checks.
The first check determines whether the double of the current number already exists in the set. This catches cases where the larger value appeared earlier.
The second check determines whether half of the current number already exists. Since only even numbers can be doubles of integers, the code first checks num % 2 == 0.
If either condition succeeds, the function immediately returns True.
If no match is found, the current number is inserted into the set so later elements can compare against it.
If the loop completes without finding any valid pair, the function returns False.
Go Solution
func checkIfExist(arr []int) bool {
seen := make(map[int]bool)
for _, num := range arr {
if seen[num*2] {
return true
}
if num%2 == 0 && seen[num/2] {
return true
}
seen[num] = true
}
return false
}
The Go implementation follows the same logic as the Python solution. Since Go does not have a built-in set type, a map of map[int]bool is used to simulate a hash set.
The lookup operations remain constant time on average.
Integer division in Go behaves correctly here because the code only checks num/2 when num is even. Therefore, no fractional values are lost.
There are no special nil-handling requirements because iterating over an empty slice is safe, although the problem guarantees at least two elements.
Worked Examples
Example 1
Input: arr = [10, 2, 5, 3]
Output: true
We trace the algorithm step by step.
| Current Number | Seen Set Before | Check Double | Check Half | Result |
|---|---|---|---|---|
| 10 | {} | 20 not found | 5 not found | Add 10 |
| 2 | {10} | 4 not found | 1 not found | Add 2 |
| 5 | {10, 2} | 10 found | not even | Return true |
When processing 5, the algorithm sees that 10 already exists in the set.
Since:
10 == 2 * 5
the answer is true.
Example 2
Input: arr = [3, 1, 7, 11]
Output: false
| Current Number | Seen Set Before | Check Double | Check Half | Result |
|---|---|---|---|---|
| 3 | {} | 6 not found | not even | Add 3 |
| 1 | {3} | 2 not found | not even | Add 1 |
| 7 | {1, 3} | 14 not found | not even | Add 7 |
| 11 | {1, 3, 7} | 22 not found | not even | Add 11 |
No valid pair is ever found, so the algorithm returns false.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each element is processed once with constant-time set lookups |
| Space | O(n) | The hash set may store all array elements |
The algorithm performs a single pass through the array. Each iteration performs only constant-time operations: hash lookups and insertions.
In the worst case, no valid pair exists, so every element is stored in the set. Therefore, the space complexity is linear in the size of the input.
Test Cases
solution = Solution()
assert solution.checkIfExist([10, 2, 5, 3]) is True # basic positive case
assert solution.checkIfExist([3, 1, 7, 11]) is False # no valid pair
assert solution.checkIfExist([0, 0]) is True # zero doubled is still zero
assert solution.checkIfExist([-2, -4]) is True # negative numbers
assert solution.checkIfExist([7, 14]) is True # direct double relationship
assert solution.checkIfExist([14, 7]) is True # reverse order relationship
assert solution.checkIfExist([1, 3, 5, 9]) is False # all odd numbers
assert solution.checkIfExist([1000, -1000]) is False # extreme values without match
assert solution.checkIfExist([2, 4, 8, 16]) is True # multiple valid pairs
assert solution.checkIfExist([1, 2]) is True # minimum size with match
assert solution.checkIfExist([1, 3]) is False # minimum size without match
assert solution.checkIfExist([-10, -5, 20]) is True # negative half relationship
assert solution.checkIfExist([6, 3]) is True # half found later
assert solution.checkIfExist([5, 10]) is True # double found later
assert solution.checkIfExist([1, 1, 1, 1]) is False # duplicates without doubling
| Test | Why |
|---|---|
[10, 2, 5, 3] |
Standard example with valid pair |
[3, 1, 7, 11] |
Standard example with no match |
[0, 0] |
Verifies correct handling of zero |
[-2, -4] |
Ensures negatives work properly |
[7, 14] |
Basic double relationship |
[14, 7] |
Confirms reverse ordering works |
[1, 3, 5, 9] |
No doubles present |
[1000, -1000] |
Boundary values without match |
[2, 4, 8, 16] |
Multiple valid pair possibilities |
[1, 2] |
Smallest valid input with match |
[1, 3] |
Smallest valid input without match |
[-10, -5, 20] |
Negative numbers with valid relationship |
[6, 3] |
Half relationship detected |
[5, 10] |
Double relationship detected |
[1, 1, 1, 1] |
Duplicate values without valid pair |
Edge Cases
One important edge case involves zeroes. Since:
0 == 2 * 0
two zeroes form a valid pair. A buggy implementation might accidentally reject this case by preventing comparisons of equal values. The algorithm handles it correctly because the first zero is added to the set, and the second zero immediately detects that its half and double already exist.
Another important edge case involves negative numbers. Some implementations incorrectly assume doubling relationships only apply to positive integers. However:
-4 == 2 * -2
is completely valid. The hash set approach treats negative values exactly the same as positive values, so the logic remains correct without modification.
A third tricky case occurs when the larger number appears before the smaller number. For example:
[10, 5]
If we only checked whether 2 * num existed earlier, we would miss this relationship. The algorithm avoids this problem by also checking whether half of the current even number already exists in the set.
Another subtle case involves duplicate values that are not zero. For example:
[1, 1, 1]
Even though duplicates exist, none of them are doubles of each other. The algorithm correctly returns False because neither the double nor valid half conditions are satisfied.