LeetCode 3289 - The Two Sneaky Numbers of Digitville
The problem is asking us to identify two numbers that appear twice in an otherwise consecutive list of integers ranging from 0 to n - 1.
Difficulty: 🟢 Easy
Topics: Array, Hash Table, Math
Solution
Problem Understanding
The problem is asking us to identify two numbers that appear twice in an otherwise consecutive list of integers ranging from 0 to n - 1. In other words, the input nums is almost a permutation of the numbers 0 through n - 1, except that two numbers are repeated, so the length of nums is n + 2.
The input is a list of integers. Each integer is guaranteed to be in the range [0, n - 1]. The output is an array of size two, containing the repeated numbers in any order. The constraints guarantee exactly two duplicates, so we do not need to handle inputs with zero, one, or more than two duplicates.
Important edge cases to consider include: the repeated numbers being consecutive, repeated numbers being at the start or end of the list, and repeated numbers being equal to the maximum (n - 1) or minimum (0) values.
Given that n is up to 100, performance is not a major concern, but we should still aim for an efficient solution that scales linearly and avoids unnecessary extra space if possible.
Approaches
Brute Force
The simplest approach is to use nested loops to check each number against every other number. For each number in the list, count how many times it occurs. If it occurs more than once, it is a repeated number. This approach is guaranteed to find the duplicates but is O(n²) in time, which is unnecessarily slow, even for small n.
Optimal Approach
A better approach uses a hash table (dictionary in Python, map in Go) to keep track of counts. As we iterate over the list, we increment the count of each number in the hash table. When the count reaches two, we know the number is repeated and we add it to the output. This guarantees O(n) time and O(n) space.
The key insight is that since each number is in a small range [0, n - 1] and there are exactly two duplicates, we can safely track occurrences with a simple hash map. There is no need for sorting or more complex techniques.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Check every pair to count duplicates |
| Hash Map | O(n) | O(n) | Track counts as we iterate; add numbers to result when count reaches 2 |
Algorithm Walkthrough
- Initialize an empty hash table (dictionary or map) to store counts of numbers.
- Initialize an empty list
resto store the two repeated numbers. - Iterate over each number in
nums. - For each number, increment its count in the hash table.
- If the count reaches 2, append the number to
resbecause it is one of the duplicates. - Continue until the end of the list; the list
reswill contain exactly two numbers. - Return
res.
Why it works: The algorithm works because we know exactly two numbers are repeated. By incrementing the count and checking when it reaches two, we capture each duplicate exactly once. The hash table ensures we can check counts in constant time.
Python Solution
from typing import List
class Solution:
def getSneakyNumbers(self, nums: List[int]) -> List[int]:
counts = {}
res = []
for num in nums:
counts[num] = counts.get(num, 0) + 1
if counts[num] == 2:
res.append(num)
return res
The implementation initializes a dictionary counts to store how many times each number appears. For every number in nums, we increment its count. When a number appears for the second time, we append it to the result list res. Finally, we return the two sneaky numbers.
Go Solution
func getSneakyNumbers(nums []int) []int {
counts := make(map[int]int)
res := []int{}
for _, num := range nums {
counts[num]++
if counts[num] == 2 {
res = append(res, num)
}
}
return res
}
The Go solution is almost identical to Python. We use a map to track counts and append numbers to a slice res when their count reaches 2. Go handles empty slices differently, but the logic is the same and safe because the slice grows dynamically.
Worked Examples
Example 1
Input: nums = [0,1,1,0]
| Iteration | num | counts | res |
|---|---|---|---|
| 1 | 0 | {0:1} | [] |
| 2 | 1 | {0:1,1:1} | [] |
| 3 | 1 | {0:1,1:2} | [1] |
| 4 | 0 | {0:2,1:2} | [1,0] |
Output: [1,0] (order does not matter)
Example 2
Input: nums = [0,3,2,1,3,2]
| Iteration | num | counts | res |
|---|---|---|---|
| 1-3 | 0,3,2 | {0:1,3:1,2:1} | [] |
| 4 | 1 | {0:1,3:1,2:1,1:1} | [] |
| 5 | 3 | {0:1,3:2,2:1,1:1} | [3] |
| 6 | 2 | {0:1,3:2,2:2,1:1} | [3,2] |
Output: [3,2]
Example 3
Input: nums = [7,1,5,4,3,4,6,0,9,5,8,2]
Duplicates found in order: 4,5
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass through the array, counting occurrences |
| Space | O(n) | Hash map stores counts of up to n distinct numbers |
The algorithm is linear in both time and space. Since n ≤ 100, this is very efficient.
Test Cases
# Provided examples
assert Solution().getSneakyNumbers([0,1,1,0]) == [1,0] or Solution().getSneakyNumbers([0,1,1,0]) == [0,1]
assert Solution().getSneakyNumbers([0,3,2,1,3,2]) == [3,2] or Solution().getSneakyNumbers([0,3,2,1,3,2]) == [2,3]
assert Solution().getSneakyNumbers([7,1,5,4,3,4,6,0,9,5,8,2]) == [4,5] or Solution().getSneakyNumbers([7,1,5,4,3,4,6,0,9,5,8,2]) == [5,4]
# Edge cases
assert Solution().getSneakyNumbers([0,0,1,2]) == [0] # Only 0 repeated at start
assert Solution().getSneakyNumbers([0,1,2,2,3]) == [2] # Only 2 repeated in middle
assert Solution().getSneakyNumbers([0,1,2,3,3]) == [3] # Only 3 repeated at end
assert Solution().getSneakyNumbers([0,1,2,3,4,4,2,5]) == [4,2] or Solution().getSneakyNumbers([0,1,2,3,4,4,2,5]) == [2,4] # Repeats out of order
| Test | Why |
|---|---|
| [0,1,1,0] | Simple two duplicates at beginning |
| [0,3,2,1,3,2] | Duplicates not adjacent, arbitrary order |
| [7,1,5,4,3,4,6,0,9,5,8,2] | Random order with larger numbers |
| [0,0,1,2] | Repeated at start of array |
| [0,1,2,2,3] | Repeated in the middle |
| [0,1,2,3,3] | Repeated at end |
| [0,1,2,3,4,4,2,5] | Repeats out of order |
Edge Cases
The first important edge case is when both repeated numbers are 0 and 1. This could trip up implementations that assume duplicates are larger numbers. Our hash map counts occurrences of all numbers equally, so this works fine.
The second edge case is when both repeated numbers are the largest in the range, n - 2 and n - 1. Any index-based method could fail if it does not handle the upper bound correctly. Using a hash map handles all values in the valid range.
The third edge case is when