Description
Single Element in a Sorted Array
You are given a sorted array consisting of only integers where every element appears exactly twice, except for one element which appears exactly once.
Return the single element that appears only once.
Your solution must run in O(log n) time and O(1) space.
Example 1:
Input: nums = [1,1,2,3,3,4,4,8,8]
Output: 2
Example 2:
Input: nums = [3,3,7,7,10,11,11]
Output: 10
Constraints:
1 <= nums.length <= 10^50 <= nums[i] <= 10^5
Brute Force Approach: Bitwise XOR
Intuition
XOR all elements together. Since and , all duplicate pairs cancel each other out, leaving only the single unique element.
class Solution {
public int singleNonDuplicate(int[] nums) {
int xorr = 0;
for (int num : nums) {
xorr ^= num;
}
return xorr;
}
}
Complexity
- Time Complexity: — Scans through all elements once.
- Space Complexity: — Uses constant extra memory.
Binary Search Approach 1: Subarray Size Check
Intuition
At index m, check if nums[m] matches its left or right neighbor to identify its pair. Calculate the size of the left portion (leftSize). If leftSize is odd, the single element lies in the left half; otherwise, it lies in the right half.
class Solution {
public int singleNonDuplicate(int[] nums) {
int l = 0, r = nums.length - 1;
while (l <= r) {
int m = l + (r - l) / 2;
// Check if m is the single element
if ((m - 1 < 0 || nums[m - 1] != nums[m]) &&
(m + 1 == nums.length || nums[m] != nums[m + 1])) {
return nums[m];
}
// Determine boundary index of the left pair
int leftSize = (m - 1 >= 0 && nums[m - 1] == nums[m]) ? m - 1 : m;
// If left subarray size is odd, target is on the left side
if (leftSize % 2 == 1) {
r = m - 1;
} else {
l = m + 1;
}
}
return -1;
}
}
Complexity
- Time Complexity: — Standard binary search halving the search space.
- Space Complexity: — Constant auxiliary space.
Binary Search Approach 2: Binary Search on Even Indices
Intuition
Before the single element, every pair starts on an even index (nums[even] == nums[even + 1]). Force m to be an even index. If nums[m] == nums[m + 1], the disruption hasn’t happened yet, so search right (l = m + 2). Otherwise, search left (r = m).
class Solution {
public int singleNonDuplicate(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int m = l + (r - l) / 2;
// Ensure m is always even
if (m % 2 != 0) {
m--;
}
// If pair matches, single element is strictly to the right
if (nums[m] == nums[m + 1]) {
l = m + 2;
} else {
r = m;
}
}
return nums[l];
}
}
Complexity
- Time Complexity: — Binary search operating over even index candidates.
- Space Complexity: — Constant auxiliary space.
Most Optimized Solution: Binary Search with Bitwise XOR Partner Index (m ^ 1)
Intuition
Use bitwise XOR (m ^ 1) to dynamically find the expected partner index of m regardless of whether m is even or odd:
- If
mis even,m ^ 1equalsm + 1. - If
mis odd,m ^ 1equalsm - 1.
If nums[m] == nums[m ^ 1], the pair is intact and in proper order, meaning we are still to the left of the single element, so move right (l = m + 1). Otherwise, the single element is at or before m (r = m).
class Solution {
public int singleNonDuplicate(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (nums[m] == nums[m ^ 1]) {
l = m + 1; // Left side intact -> search right
} else {
r = m; // Single element is at or to the left of m
}
}
return nums[l];
}
}
Complexity
- Time Complexity: — Halves the search space each step.
- Space Complexity: — Uses no extra space.
Easy Memory Rule
“For single element:
m ^ 1finds partner index. Ifnums[m] == nums[m ^ 1]l = m + 1, elser = m.”
When you reach the else statement (nums[m] != nums[m + 1]), nums[m] could be the single element, but it could also be the second half of a pair located somewhere to the right of the single element.
Concrete Example
Consider this array where 3 at index 4 is the single element:
Suppose binary search chooses m = 6 (which is an even index):
nums[6]is4nums[6 + 1](index 7) is5- Check:
nums[6] == nums[7]4 == 5is False!
We enter the else block:
- Is
nums[6](value4) our single element? No! - Value
4is actually part of the pair[4, 4]at indices(5, 6). - Because the single element
3appears at index 4, it shifted all subsequent pairs so they now start on odd indices instead of even indices (4is at indices5, 6instead of6, 7).
Why we do r = m
Entering the else branch only proves one thing:
“The disruption (single element) has already happened at or before index
m.”
- It could be at index
m(e.g., ifm = 4,nums[4] = 3 != nums[5] = 4). - It could be somewhere to the left of
m(e.g., ifm = 6, the single element3is at index 4).
Because nums[m] might actually be the answer, we cannot eliminate m completely (so we don’t do r = m - 1). Instead, we set r = m to keep index m inside our search window and continue searching the left half.