Topics › Binary Search › BS on 1D Arrays
Single element in a Sorted Array
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given a sorted array arr of n integers in which every value appears exactly twice, except for one value that appears exactly once. Return that single value. Because the array is sorted, the two copies of every duplicated value sit next to each other, and n is always odd.
Constraints: 1 ≤ n ≤ 10^5, n is odd, values fit in a 32-bit signed integer. Exactly one value appears once; every other value appears exactly twice.
Example 1
arr = [1, 1, 2, 3, 3, 4, 4, 8, 8]
answer = 2 // every other value has a twin next to it
Example 2
arr = [3, 3, 7, 7, 10, 11, 11]
answer = 10 // 10 sits between the pair (7, 7) and the pair (11, 11)
Approach 1: Brute force, compare with the neighbours
Intuition. Because duplicates are adjacent, the single element is the only one whose value differs from both its left neighbour and its right neighbour. The first and last positions have only one neighbour, so they need their own comparison. A single left-to-right scan finds it.
Algorithm.
- If
n == 1, the only element is the answer. - For every index
i:- If
i == 0, returnarr[0]whenarr[0] != arr[1]. - If
i == n - 1, returnarr[n-1]whenarr[n-1] != arr[n-2]. - Otherwise return
arr[i]when it differs from botharr[i-1]andarr[i+1].
- If
- The loop always finds the answer for valid input;
-1is returned only for malformed arrays.
static int bruteForce(int[] arr) {
int n = arr.length;
if (n == 1) return arr[0];
for (int i = 0; i < n; i++) {
if (i == 0) {
if (arr[0] != arr[1]) return arr[0];
} else if (i == n - 1) {
if (arr[n - 1] != arr[n - 2]) return arr[n - 1];
} else if (arr[i] != arr[i - 1] && arr[i] != arr[i + 1]) {
return arr[i];
}
}
return -1;
}
Complexity. Time O(n): each index is examined once with a constant number of comparisons. Space O(1): no extra storage.
This is already linear, but the problem explicitly asks for O(log n), which is the real exercise here. It is also worth noticing that this approach does not depend on sortedness beyond “duplicates are adjacent”.
Approach 2: Better, XOR everything
Intuition. XOR has two properties that make it perfect for “everything appears twice except one”: a ^ a = 0 and a ^ 0 = a, and the operation is commutative and associative, so the order of the elements does not matter. XOR-ing the whole array makes every pair cancel to 0, leaving exactly the single value.
Algorithm.
- Start with
x = 0. - XOR every element into
x. - Return
x.
static int better(int[] arr) {
int x = 0;
for (int v : arr) x ^= v;
return x;
}
Complexity. Time O(n): one pass. Space O(1).
This is the neatest linear solution and it even works for unsorted arrays. It is not faster than Approach 1 asymptotically, but it is branch-free and worth knowing because the same trick solves several “find the odd one out” problems. It still does not use the sorted order, so it cannot reach O(log n).
Approach 3: Optimal, binary search on index parity
Intuition. Look at the indices of each pair. Before the single element, pairs occupy positions (0,1), (2,3), (4,5), ...: the first copy is at an even index and the second at an odd index. After the single element, everything is shifted by one: pairs occupy (odd, even) positions instead. So at any index mid we can tell which side of the single element we are on by checking whether arr[mid] pairs with the “expected” neighbour:
- If
midis even andarr[mid] == arr[mid+1], the pair starts at an even index, so we are in the left half; the single element is to the right. - If
midis odd andarr[mid] == arr[mid-1], the pair again starts at an even index (mid-1), so again we are in the left half. - Otherwise the parity is broken, which means the single element is at or before
mid, so we move left.
If arr[mid] matches neither neighbour, it is the single element.
To avoid index checks inside the loop, handle the two ends up front: if arr[0] != arr[1] the answer is arr[0], and if arr[n-1] != arr[n-2] the answer is arr[n-1]. After that the search range [1, n-2] always has a valid neighbour on both sides.
Algorithm.
- If
n == 1, returnarr[0]. Ifarr[0] != arr[1], returnarr[0]. Ifarr[n-1] != arr[n-2], returnarr[n-1]. - Set
lo = 1,hi = n - 2. - While
lo <= hi:mid = lo + (hi - lo) / 2.- If
arr[mid]differs from both neighbours, return it. - Determine
inLeftHalf: for oddmidit isarr[mid] == arr[mid-1], for evenmidit isarr[mid] == arr[mid+1]. - If
inLeftHalf, setlo = mid + 1; otherwise sethi = mid - 1.
static int optimal(int[] arr) {
int n = arr.length;
if (n == 1) return arr[0];
if (arr[0] != arr[1]) return arr[0];
if (arr[n - 1] != arr[n - 2]) return arr[n - 1];
int lo = 1, hi = n - 2;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid] != arr[mid - 1] && arr[mid] != arr[mid + 1]) return arr[mid];
// Left of the single element every pair starts at an even index: (0,1), (2,3), ...
boolean inLeftHalf = (mid % 2 == 1) ? arr[mid] == arr[mid - 1] : arr[mid] == arr[mid + 1];
if (inLeftHalf) lo = mid + 1;
else hi = mid - 1;
}
return -1;
}
Dry run on arr = [1, 1, 2, 3, 3, 4, 4, 8, 8] (n = 9). The ends are both proper pairs, so we search [1, 7].
| lo | hi | mid | arr[mid-1], arr[mid], arr[mid+1] | mid parity | inLeftHalf? | action |
|---|---|---|---|---|---|---|
| 1 | 7 | 4 | 3, 3, 4 | even | arr[4] == arr[5]? no | hi = 3 |
| 1 | 3 | 2 | 1, 2, 3 | even | differs from both | return 2 |
Why does the half we discard never contain the answer? If inLeftHalf is true, the pair containing mid starts at an even index, which is only possible when no single element precedes it, so indices ≤ mid + 1 are all proper pairs and the answer is strictly to the right. If it is false, the pair containing mid starts at an odd index, which can only happen after the single element, so the answer is strictly to the left. Each step therefore halves the range while keeping the answer inside it.
Complexity. Time O(log n): the range is halved on every iteration. Space O(1).
Edge cases and pitfalls
- Single element array.
[1]must return1; both the brute force and the binary search handlen == 1before touchingarr[1]. - Answer at the ends.
[1, 2, 2, 3, 3]and[1, 1, 2, 2, 3]are the two boundary cases. The optimal method checks them before the loop so thatarr[mid - 1]andarr[mid + 1]are always valid. - Even and odd mid mixed up. The parity rule is about the index of the first copy of a pair. Writing
arr[mid] == arr[mid + 1]for oddmidreverses the direction and the search diverges. The dry run table is a good self-check. - Negative numbers and zero. Nothing changes; the comparisons are equality tests. The Java tests include
[-5, -5, -3, -1, -1]and an array starting with0, 0. - Unsorted input. Only the XOR approach is correct on an unsorted array. The binary search relies on pairs being adjacent and in parity order.
- Using
mid % 2with a negativemidis never an issue here because indices are non-negative, but remember that Java’s%can return negative values in general.
Related problems
- Find peak element is another binary search where the array is not globally sorted but a local comparison still tells you which half to keep.
- Kth Missing Positive Number also derives the search direction from a relationship between the value and its index.