题目
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
示例 1:
输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: [3,3,7,7,10,11,11]
输出: 10
注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。
思路 1:根据特征二分
在唯一的数出现之前,连续相同的两个元素下标是偶奇,出现之后就是奇偶,根据这一特征来对整个数组进行二分查找。具体讲一下如何判断唯一数在 mid
指针的哪一边:
- 如果
mid == 0
,如果nums[0] == nums[1]
,唯一数要去右边找,否则唯一数就是mid
- 如果
mid == nums.size()-1
,如果nums[nums.size()-2] == nums[nums.size()-1]
,唯一数要去左边找,否则唯一数就是mid
- 排除上面两种情况,
mid
就在中间了,如果mid
和两侧元素都不一样,就找到了 - 其他情况得先看
mid
本身是奇数偶数再分类讨论
这个思路的问题在于判断的过程有点复杂了,所以代码中 checkPosition
函数写了那么多行,官方题解给出了思路 2,简洁很多。
时间复杂度:
O
(
log
n
)
O(\log n)
O(logn)
空间复杂度:
O
(
1
)
O(1)
O(1)
C++ 代码
class Solution {
public:
// 在唯一的数出现之前,连续相同的两个元素下标是偶奇,出现之后就是奇偶
// 查找index和唯一数的相对位置,重合返回0,唯一数在index左边返回-1,右边返回1
int checkPosition(vector<int>& nums, int index) {
int size = nums.size();
// 特判index在最左侧的情况
if (index == 0) {
if (nums[0] == nums[1])
return 1;
else
return 0;
}
// 特判index在最右侧的情况
if (index == size - 1) {
if (nums[size - 2] == nums[size - 1])
return -1;
else
return 0;
}
// index在中间的情况
// 如果和两侧的都不同,就找到了
if (nums[index] != nums[index - 1] && nums[index] != nums[index + 1])
return 0;
// 否则根据和nums[index - 1]是否相同来判断唯一数在左侧还是右侧
if (index % 2) {
if (nums[index] == nums[index - 1])
return -1;
else
return 1;
} else {
if (nums[index] == nums[index - 1])
return 1;
else
return -1;
}
}
int singleNonDuplicate(vector<int>& nums) {
int size = nums.size(), left = 0, right = size - 1, mid;
if (size == 1)
return nums[0];
while (left < right) {
mid = left + (right - left) / 2;
if (checkPosition(nums, mid) == 0)
return nums[mid];
else if (checkPosition(nums, mid) == 1)
right = mid;
else
left = mid + 1;
}
return nums[left];
}
};
思路 2:只对偶数下标进行二分搜索
容易证明,唯一数的下标一定是个偶数,并且数组长度一定是个奇数(所以最后一个下标是偶数)。所以只对偶数下标进行二分搜索即可。在唯一数右侧的偶数下标,所指的数和左侧的数相同,在唯一数左侧的偶数下标,所指的数和右侧的数相同,根据这一特点进行二分。
代码中二分的初始范围是 [0, (size - 1) / 2],这是所有偶数下标的数量,进行判断的时候将其乘以 2 就是实际的下标大小了。
C++ 代码
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int size = nums.size(), lo = 0, hi = (size - 1) / 2, mid;
while (lo < hi) {
mid = lo + (hi - lo) / 2;
int realMid = mid * 2;
if (realMid < size && nums[realMid] == nums[realMid + 1])
lo = mid + 1;
else if (realMid > 0 && nums[realMid] == nums[realMid - 1])
hi = mid;
else
return nums[realMid];
}
return nums[lo * 2];
}
};