Every element in a sorted array appears exactly twice except one. Find that lonely value in O(log n) by exploiting how the pairs line up: before the single element each pair starts on an even index, and that parity flips the moment the single element appears.
Given a sorted array nums in which every value appears exactly twice except one value that appears once, return that single value. For example nums=[1,1,2,3,3,4,4,8,8] → the answer is 2 (the only un-paired number). Another: nums=[3,3,7,7,10,11,11] → 10. The required complexity is O(log n) time and O(1) space, which rules out a simple linear XOR or scan.
(0,1), (2,3), (4,5)…. Beforethe single element, every pair's first copy sits at an even index, so nums[even] === nums[even + 1]. Afterthe single element, the pairing is shifted by one and that equality breaks. So "is the pairing still intact at this even index?" is a monotone yes→no predicate — exactly what binary search needs.lo = 0, hi = n - 1. We shrink until lo === hi, which lands on the answer.< (not <=) because hi = mid keeps mid in the window — equality would loop forever.mid = lo + ⌊(hi - lo)/2⌋, then if (mid % 2 === 1) mid-- (equivalently mid -= mid & 1). Now mid is the candidate start of a pair, so mid + 1 is always in range.nums[mid] === nums[mid + 1] the pairing is intact up to here, so the single element is strictly to the right → lo = mid + 2. Otherwise the break is at mid or earlier → hi = mid (keep mid — it might be the answer).lo === hi points at the single element. Return nums[lo].lo = mid + 2 but hi = mid (asymmetric)? Because mid is forced even and the pair (mid, mid+1) matched, both cells are ruled out, so jump past them. In the else-branch mid itself can be the lonely element, so it must stay in the window — hence hi = mid, not mid - 1.nums.reduce((a, b) => a ^ b, 0) returns the single value in O(n) because each duplicate XORs to zero. It is beautiful but reads the whole array — the problem explicitly demands O(log n), so you must use the sorted/pairing structure.
lo=0, hi=8. Amber cells are the live window; we converge until lo === hi.1function singleNonDuplicate(nums: number[]): number {2▶ let lo = 0;3▶ let hi = nums.length - 1;45 while (lo < hi) {6 let mid = lo + Math.floor((hi - lo) / 2);7 if (mid % 2 === 1) mid--; // force mid to be even89 if (nums[mid] === nums[mid + 1]) { // pairing intact → single is right10 lo = mid + 2;11 } else { // pairing broken → single is here or left12 hi = mid;13 }14 }15 return nums[lo]; // lo === hi → the lonely element16}
function singleNonDuplicate(nums: number[]): number {
let lo = 0;
let hi = nums.length - 1;
while (lo < hi) {
let mid = lo + Math.floor((hi - lo) / 2);
if (mid % 2 === 1) mid--; // force mid to be even
if (nums[mid] === nums[mid + 1]) { // pairing intact → single is right
lo = mid + 2;
} else { // pairing broken → single is here or left
hi = mid;
}
}
return nums[lo]; // lo === hi → the lonely element
}lo = 0 and hi = nums.length - 1. Unlike classic target search we are not looking for a value but converging two pointers onto a single surviving index.hi = mid (keeping mid), a <= condition could leave lo === hi === mid and spin forever. The window shrinks until exactly one index remains.mid is the start of a pair, so its partner mid + 1 is guaranteed in range and we can compare a clean pair.nums[mid] === nums[mid + 1] the pairing has held up to here, so the lonely element is strictly to the right → lo = mid + 2. Otherwise the parity already broke at or before mid → hi = mid (we must keep mid because it could itself be the answer).lo === hi; that index is the single element, so return nums[lo].nums[mid] with its partner chosen by parity: mid ^ 1 gives the sibling index (even→+1, odd→−1). If they are equal the single is to the right, else left. Same O(log n), no explicit adjustment.nums.reduce((a, b) => a ^ b, 0). Pairs cancel, leaving the single value. Simple and clever, but O(n) — fails the stated O(log n) requirement.lo always lands there.function singleNonDuplicate(nums: number[]): number {
let lo = 0;
let hi = nums.length - 1;
while (lo < hi) {
let mid = lo + Math.floor((hi - lo) / 2);
if (mid % 2 === 1) mid--; // force mid to be even
if (nums[mid] === nums[mid + 1]) { // pairing intact → single is right
lo = mid + 2;
} else { // pairing broken → single is here or left
hi = mid;
}
}
return nums[lo]; // lo === hi → the lonely element
}Ignore the sorted structure and XOR every element together. Each duplicated value cancels to zero, leaving only the single element — elegant and one line, but O(n).
function singleNonDuplicate(nums: number[]): number {
return nums.reduce((acc, x) => acc ^ x, 0);
}Instead of forcing mid even, compare it to its parity sibling mid ^ 1 (even→+1, odd→−1).
function singleNonDuplicate(nums: number[]): number {
let lo = 0, hi = nums.length - 1;
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2);
if (nums[mid] === nums[mid ^ 1]) lo = mid + 1; // pair intact → go right
else hi = mid; // break here or left
}
return nums[lo];
}mid ^ 1 flips the lowest bit, mapping each index to its partner in the pair. Functionally identical to forcing mid even — pick whichever you find clearer.| sorted, all paired except one, need O(log n) | binary search on pair parity |
| monotone yes→no boundary in an array | binary search the predicate, hi = mid |
| no O(log n) constraint, just find the unpaired | XOR-fold all elements (O(n)) |
| need the sibling of an index by parity | use mid ^ 1 (even↔odd partner) |
function singleNonDuplicate(nums: number[]): number {
let lo = 0, hi = nums.length - 1;
while (lo < hi) {
let mid = lo + Math.floor((hi - lo) / 2);
mid -= mid & 1; // make mid even
if (nums[mid] === nums[mid + 1]) lo = mid + 2;
else hi = mid;
}
return nums[lo];
}nums[even] === nums[even + 1]. After it, that equality breaks — a monotone yes→no boundary.nums = [1,1,2,3,3,4,4,8,8], what value does the algorithm return?mid forced to an even index with if (mid % 2 === 1) mid--?nums[mid] === nums[mid + 1] (mid even), what do you do?hi = mid rather than hi = mid - 1. Why?lo < hi instead of lo <= hi?