ID: 540
TITLE: 有序数组中的单一元素
TAG: Java,Python,C++,二分搜索
我们可以使用线性搜索来检查数组中的每个元素,直到找到单个元素。
算法:
class Solution {
public int singleNonDuplicate(int[] nums) {
for (int i = 0; i < nums.length - 1; i+=2) {
if (nums[i] != nums[i + 1]) {
return nums[i];
}
}
return nums[nums.length - 1];
}
}
def singleNonDuplicate(self, nums: List[int]) -> int:
for i in range(0, len(nums) - 2, 2):
if nums[i] != nums[i + 1]:
return nums[i]
return nums[-1]
class Solution {
public:
int singleNonDuplicate(vector& nums) {
for (int i = 0; i < nums.size() - 1; i += 2) {
if (nums[i] != nums[i + 1]) {
return nums[i];
}
}
return nums.back();
}
};
复杂度分析
尽管这个解决方案可行,但是问题中要求我们使用时间复杂度在 O ( log n ) O(\log n) O(logn) 的解决方案。因此,该解决方案还不够好。
我们将线性搜索转换为二分搜索是有意义的,它能加快我们的效率。为了使用二分搜索,我们需要查看中间的元素来判断我们的答案在中间,左边还是右边。我们的数组个数始终是奇数,因为有一个元素出现一次,其余元素出现两次。
下面是当我们从中心移除一对元素时发生的情况。将剩下左子数组和右子数组。
与原数组一样,包含单个元素的子数组元素个数必为奇数,不包含单个元素的子数组必为偶数。 因此,当原数组移除一对元素后,然后计算出哪一侧的子数组元素个数是奇数,这样我们就能够知道下一步应该在哪一测进行搜索。
算法:
lo
和 hi
指向数组首尾两个元素。然后进行二分搜索将数组搜索空间减半,直到找到单一元素或者仅剩一个元素为止。当搜索空间只剩一个元素,则该元素就是单个元素。mid
,变量 halvesAreEven = (hi - mid) % 2 == 0
。 通过查看中间元素同一元素为哪一个(左侧子数组中的最后一个元素或右侧子数组中的第一个元素),我们可以通过变量 halvesAreEven
确定现在哪一侧元素个数为奇数,并更新 lo
和 hi
。mid
和 halvesAreEven
的值正确更新 lo
和 hi
。我们通过下图来帮助我们理解。例子 1:中间元素的同一元素在右边,且被 mid
分成两半的数组为偶数。
我们将右子数组的第一个元素移除后,则右子数组元素个数变成奇数,我们应将 lo
设置为 mid + 2
。
例子 2:中间元素的同一元素在右边,且被 mid
分成两半的数组为奇数。
我们将右子数组的第一个元素移除后,则右子数组的元素个数变为偶数,我们应将 hi
设置为 mid - 1
。
例子 3:中间元素的同一元素在左边,且被 mid
分成两半的数组为偶数。
我们将左子数组的最后一个元素移除后,则左子数组的元素个数变为奇数,我们应将 hi
设置为 mid - 2
。
例子 4:中间元素的同一元素在左边,且被 mid
分成两半的数组为奇数。
我们将左子数组的最后一个元素移除后,则左子数组的元素个数变为偶数,我们应将 lo
设置为 mid + 1
。
class Solution {
public int singleNonDuplicate(int[] nums) {
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
boolean halvesAreEven = (hi - mid) % 2 == 0;
if (nums[mid + 1] == nums[mid]) {
if (halvesAreEven) {
lo = mid + 2;
} else {
hi = mid - 1;
}
} else if (nums[mid - 1] == nums[mid]) {
if (halvesAreEven) {
hi = mid - 2;
} else {
lo = mid + 1;
}
} else {
return nums[mid];
}
}
return nums[lo];
}
}
def singleNonDuplicate(self, nums: List[int]) -> int:
lo = 0
hi = len(nums) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
halves_are_even = (hi - mid) % 2 == 0
if nums[mid + 1] == nums[mid]:
if halves_are_even:
lo = mid + 2
else:
hi = mid - 1
elif nums[mid - 1] == nums[mid]:
if halves_are_even:
hi = mid - 2
else:
lo = mid + 1
else:
return nums[mid]
return nums[lo]
class Solution {
public:
int singleNonDuplicate(vector& nums) {
int lo = 0;
int hi = nums.size() - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
bool halvesAreEven = (hi - mid) % 2 == 0;
if (nums[mid + 1] == nums[mid]) {
if (halvesAreEven) {
lo = mid + 2;
} else {
hi = mid - 1;
}
} else if (nums[mid - 1] == nums[mid]) {
if (halvesAreEven) {
hi = mid - 2;
} else {
lo = mid + 1;
}
} else {
return nums[mid];
}
}
return nums[lo];
}
};
另外,你会发现即使数组没有经过排序,只要将同一元素放在一起,该算法仍然起作用(例:[10, 10, 4, 4, 7, 11, 11, 12, 12, 2, 2]
)。他们的顺序无关紧要,重要的是含有单个元素的子数组元素个数为奇数。
复杂度分析
算法:
lo
和 hi
设置为数组首尾。mid
是偶数,如果为奇数,则将其减 1
。mid
的元素是否与其后面的索引相同。mid
不是单个元素。且单个元素在 mid
之后。则我们将 lo
设置为 mid + 2
。mid
,或者在 mid
之前。我们将 hi
设置为 mid
。lo == hi
,则当前搜索空间为 1 个元素,那么该元素为单个元素,我们将返回它。class Solution {
public int singleNonDuplicate(int[] nums) {
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (mid % 2 == 1) mid--;
if (nums[mid] == nums[mid + 1]) {
lo = mid + 2;
} else {
hi = mid;
}
}
return nums[lo];
}
}
def singleNonDuplicate(self, nums: List[int]) -> int:
lo = 0
hi = len(nums) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if mid % 2 == 1:
mid -= 1
if nums[mid] == nums[mid + 1]:
lo = mid + 2
else:
hi = mid
return nums[lo]
class Solution {
public:
int singleNonDuplicate(vector& nums) {
int lo = 0;
int hi = nums.size() - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (mid % 2 == 1) mid--;
if (nums[mid] == nums[mid + 1]) {
lo = mid + 2;
} else {
hi = mid;
}
}
return nums[lo];
}
};
复杂度分析