题目地址:
https://leetcode.com/problems/single-element-in-a-sorted-array/
给定一个从小到大排好序的数组 A A A,除了其中一个数字只出现了一次外,每个数字都出现了两次,要求找到那个只出现了一次的数字。
法1:位运算。一路异或一遍即可。代码如下:
class Solution {
public int singleNonDuplicate(int[] nums) {
int res = 0;
for (int n : nums) {
res ^= n;
}
return res;
}
}
时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1)。
比下文更好的二分思路参考https://blog.csdn.net/qq_46105170/article/details/113623636。
法2:二分。考虑将区间二分后如何判断那个唯一的数应该在哪个半边。设二分中点为 m m m,区间左右端点分别为 l l l和 r r r,那么区间中比 A [ m ] A[m] A[m]大的数的个数可以这样计算:如果 A [ m ] = A [ m + 1 ] A[m]=A[m+1] A[m]=A[m+1],那么个数是 r − m − 1 r-m-1 r−m−1;否则个数是 r − m r-m r−m。而由于除了那个落单的数以外,其他数都是两两配对的,所以如果比 A [ m ] A[m] A[m]大的数的个数是偶数个,那么那个唯一的数一定在左半边,否则在右半边。这样就可以缩小范围了。代码如下:
public class Solution {
public int singleNonDuplicate(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int m = l + (r - l >> 1);
if (nums[m] == nums[m + 1]) {
int larger = r - m - 1;
// 如果比A[m]大的数是偶数个,则答案在左边,且不能是A[m],所以令r = m - 1;
// 如果比A[m]大的数是奇数个,则答案在右边,且不能是A[m],所以令l = m + 2;
if (larger % 2 == 0) {
r = m - 1;
} else {
l = m + 2;
}
} else {
// 此时nums[m] != nums[m + 1],则nums[m + 1] > nums[m]。
int larger = r - m;
// 如果比A[m]大的数是偶数个,则答案在左边,且可能是A[m],所以令r = m;
// 如果比A[m]大的数是奇数个,则答案在右边,且不可能是A[m](因为题目保证答案是唯一的),所以令r = m - 1;
if (larger % 2 == 0) {
r = m;
} else {
l = m + 1;
}
}
}
return nums[l];
}
}
时间复杂度 O ( log n ) O(\log n) O(logn),空间 O ( 1 ) O(1) O(1)。