🧠
Binary Search Using Index Parity
💡 This approach introduces the key insight that pairs start at even indices before the single element and at odd indices after it. It leverages this to perform a binary search, reducing time complexity drastically.
Intuition
Because the array is sorted and pairs appear consecutively, the single element disrupts the pattern. By checking the parity of the mid index and comparing with neighbors, we can decide which half to discard.
Algorithm
- Initialize low and high pointers at start and end of array.
- While low < high, find mid index.
- Check if mid is even or odd.
- If mid is even and nums[mid] == nums[mid + 1], move low to mid + 2; else move high to mid.
- If mid is odd and nums[mid] == nums[mid - 1], move low to mid + 1; else move high to mid - 1.
- When low == high, return nums[low] as the single element.
💡 The parity check helps decide which half contains the single element, halving the search space each iteration.
def singleNonDuplicate(nums):
low, high = 0, len(nums) - 1
while low < high:
mid = (low + high) // 2
if mid % 2 == 0:
if nums[mid] == nums[mid + 1]:
low = mid + 2
else:
high = mid
else:
if nums[mid] == nums[mid - 1]:
low = mid + 1
else:
high = mid - 1
return nums[low]
# Driver code
if __name__ == "__main__":
print(singleNonDuplicate([1,1,2,3,3,4,4,8,8])) # Output: 2
Line Notes
low, high = 0, len(nums) - 1Initialize search boundaries
while low < high:Loop until search space reduces to one element
mid = (low + high) // 2Find middle index to check
if mid % 2 == 0:Check parity of mid to decide pairing logic
if nums[mid] == nums[mid + 1]:If pair is intact, single element is after mid
low = mid + 2Discard left half including mid and its pair
else: high = midSingle element is at mid or before
return nums[low]When low meets high, single element found
public class Solution {
public static int singleNonDuplicate(int[] nums) {
int low = 0, high = nums.length - 1;
while (low < high) {
int mid = (low + high) / 2;
if (mid % 2 == 0) {
if (nums[mid] == nums[mid + 1]) {
low = mid + 2;
} else {
high = mid;
}
} else {
if (nums[mid] == nums[mid - 1]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
}
return nums[low];
}
public static void main(String[] args) {
int[] nums = {1,1,2,3,3,4,4,8,8};
System.out.println(singleNonDuplicate(nums)); // Output: 2
}
}
Line Notes
int low = 0, high = nums.length - 1;Initialize binary search bounds
while (low < high)Continue until search space is one element
int mid = (low + high) / 2;Calculate middle index
if (mid % 2 == 0)Check if mid is even to apply pairing logic
if (nums[mid] == nums[mid + 1])If pair intact, single element is right
low = mid + 2;Discard left half including pair
else high = mid;Single element is at or before mid
return nums[low];Return the single element found
#include <iostream>
#include <vector>
using namespace std;
int singleNonDuplicate(vector<int>& nums) {
int low = 0, high = nums.size() - 1;
while (low < high) {
int mid = (low + high) / 2;
if (mid % 2 == 0) {
if (nums[mid] == nums[mid + 1]) {
low = mid + 2;
} else {
high = mid;
}
} else {
if (nums[mid] == nums[mid - 1]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
}
return nums[low];
}
int main() {
vector<int> nums = {1,1,2,3,3,4,4,8,8};
cout << singleNonDuplicate(nums) << endl; // Output: 2
return 0;
}
Line Notes
int low = 0, high = nums.size() - 1;Set initial binary search range
while (low < high)Loop until narrowed to one element
int mid = (low + high) / 2;Calculate midpoint index
if (mid % 2 == 0)Use parity to check pair alignment
if (nums[mid] == nums[mid + 1])If pair intact, single element is right
low = mid + 2;Move low pointer past the pair
else high = mid;Single element is at or before mid
return nums[low];Return the single element found
function singleNonDuplicate(nums) {
let low = 0, high = nums.length - 1;
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (mid % 2 === 0) {
if (nums[mid] === nums[mid + 1]) {
low = mid + 2;
} else {
high = mid;
}
} else {
if (nums[mid] === nums[mid - 1]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
}
return nums[low];
}
// Test
console.log(singleNonDuplicate([1,1,2,3,3,4,4,8,8])); // Output: 2
Line Notes
let low = 0, high = nums.length - 1;Initialize binary search boundaries
while (low < high)Loop until search space is one element
let mid = Math.floor((low + high) / 2);Calculate midpoint index
if (mid % 2 === 0)Check parity of mid index
if (nums[mid] === nums[mid + 1])If pair intact, single element is right
low = mid + 2;Discard left half including pair
else high = mid;Single element is at or before mid
return nums[low];Return the single element found
Binary search halves the search space each iteration, leading to logarithmic time. Space is constant as no extra data structures are used.
💡 For n=100,000 elements, this means about 17 operations, which is extremely efficient.
Interview Verdict: Accepted and optimal
This is the preferred approach in interviews due to its efficiency and elegance.