540. 有序数组中的单一元素
- 1. 题目描述
- 2.详细题解
- 3.代码实现
- 3.1 Python
- 3.2 Java
1. 题目描述
题目中转:540. 有序数组中的单一元素
2.详细题解
方法一:若不限定时间复杂度,则扫描遍历一遍即可找到仅出现一次的数,具体实现见Python实现方法一。
方法二:若不限定空间复杂度,借助集合实现,具体实现见Python实现方法二,基本原理为:对数组求集合,再对集合中所有数字的和乘以2,结果假定为
X
X
X,此时
X
X
X的结果包括所有出现两次的数字之和,且对单独出现的数字也计算了两次,再对数组求和结果假定为
Y
Y
Y,
X
−
Y
X-Y
X−Y的差值即为单独出现的数字。
方法三——二分查找(根据子区间个数奇偶):限定的时间复杂度:
O
(
l
o
g
n
)
O(log n)
O(logn),空间复杂度:
O
(
1
)
O(1)
O(1)的情况下,结合数组有序,此时可以利用二分查找算法进行查找,但需要变型。数组中除了一个元素仅出现一次,其它元素均出现两次,因此数组的总长度为奇数,二分查找时的中位数恰好将数组等分,且一分为二后的左右区间的元素个数相同,元素个数可能为偶数或奇数。
- 对于偶数个数:此时中位数与左元素或者右元素谁相等,则单独元素则在那个区间;否则中位数即为单独出现的数字。【为什么呢?原理在于,一分为二后,左区间或右区间元素个数为偶数,如果不含单独的数字,则该区间的元素都是成双出现,不会出现与中位数相等的情况】
- 对于奇数个数:此时中位数与左元素或者右元素谁相等,则单独元素则不在那个区间,否则中位数即为单独出现的数字。【为什么呢?原理在于,一分为二后,左区间或右区间元素个数为奇数,如果不含单独的数字,则该区间的元素加上中位数正好均成双出现】
具体算法如下: - Step1:初始化:两个指针 l e f t left left 和 r i g h t right right,分别指向数组的起始和结束位置;
- Step2:计算中间元素的索引: m i d = ( l e f t + r i g h t ) / 2 mid = (left + right) / 2 mid=(left+right)/2;
- Step3:如果 n u m s [ m i d ] = = n u m s [ m i d − 1 ] nums[mid] == nums[mid-1] nums[mid]==nums[mid−1]:
- 如果nums[mid] >= nums[left]的值,说明左区间有序;
- 否则右区间有序;
- Step4:如果 n u m s [ m i d ] = = n u m s [ m i d + 1 ] nums[mid] == nums[mid+1] nums[mid]==nums[mid+1](同理):
- 如果目标值在左区间内,则更新 r i g h t = m i d − 1 right=mid-1 right=mid−1;
- 否则在右区间内,则更新 l e f t = m i d + 1 left = mid + 1 left=mid+1;
- Step5:否则 m i d mid mid元素即为单独出现的数字,返回即可:
- Step6:当指针 l e f t left left小于 r i g h t right right时,重复步骤Step2_Step6;
- Step7:否则循环结束返回 n u m s [ l e f t ] nums[left] nums[left]。
该算法具体实现见Python实现方法三。
方法四——二分查找(根据成双元素出现在单独数字的左右侧而下标奇偶性不同查找):对于数组中单独出现数字之前成双出对的元素来说,其索引下标均为先偶数再奇数,而单独出现数字后成双出对的元素来说,其索引下标均为先奇数再偶数,那么则可以利用该性质使用二分查找算法,对于
m
i
d
mid
mid判断其奇偶性,结合其是与前一个元素相等或者与后一个元素相等,从而判断单独出现的数字在那个区间。【具体的,当
m
i
d
mid
mid为偶数且与
m
i
d
+
1
mid+1
mid+1元素相等(或
m
i
d
mid
mid为奇数且与前一个元素
m
i
d
−
1
mid-1
mid−1元素相等),则单独出现的数字在右区间,否则在左区间;<
m
i
d
mid
mid为偶数时与加1判断,奇数时与减1判断,可使用异或统一计算】
具体算法如下:
- Step1:初始化:两个指针 l e f t left left 和 r i g h t right right,分别指向数组的起始和结束位置;
- Step2:计算中间元素的索引: m i d = ( l e f t + r i g h t ) / 2 mid = (left + right) / 2 mid=(left+right)/2;
- Step3:如果 n u m s [ m i d ] = n u m s [ m i d 异或 1 ] nums[mid] = nums[mid异或1] nums[mid]=nums[mid异或1]:
- n u m s [ m i d ] = n u m s [ m i d 异或 1 ] nums[mid] = nums[mid异或1] nums[mid]=nums[mid异或1]成立,说明 m i d mid mid在单独出现数字的左侧,故单独出现数字在右区间,更新 l e f t = m i d + 1 left=mid+1 left=mid+1;
- Step4:否则,说明 m i d mid mid在单独出现数字的右侧,故单独出现数字在左区间,更新 r i g h t = m i d right=mid right=mid;
- Step5:当指针 l e f t left left小于 r i g h t right right时,重复步骤Step2_Step5;
- Step6:否则循环结束返回 n u m s [ l e f t ] nums[left] nums[left]。
该算法具体实现见Python实现方法四。
方法五——二分查找(根据单独出现数字的索引奇偶性判断):对于数组中单独出现的数字,其它元素均成双出现,故单独出现数字的索引肯定为偶数,若下标为
x
x
x,则
n
u
m
s
[
x
]
!
=
n
u
m
s
[
x
+
1
]
nums[x]!=nums[x+1]
nums[x]!=nums[x+1],故可利用此性质进行二分查找,查找最小的
x
x
x即为单独出现的数字;具体的,在偶数索引的下标范围内进行查找。
具体算法如下:
- Step1:初始化:两个指针 l e f t left left 和 r i g h t right right,分别指向数组的起始和最后一个偶数索引,由于数字长度为奇数,因此最后一个偶数索引即为数组的结束位置;
- Step2:计算中间元素的索引: m i d = ( l e f t + r i g h t ) / 2 mid = (left + right) / 2 mid=(left+right)/2;
- Step3:如果 m i d mid mid为奇数,则 m i d = m i d − 1 mid=mid-1 mid=mid−1,确保 m i d mid mid为偶数:
- 如果 n u m s [ m i d ] = n u m s [ m i d + 1 ] nums[mid] = nums[mid+1] nums[mid]=nums[mid+1]成立,说明 m i d mid mid在单独出现数字的左侧,故单独出现数字在右区间,更新 l e f t = m i d + 2 left=mid+2 left=mid+2;
- 如果 n u m s [ m i d ] = n u m s [ m i d + 1 ] nums[mid] = nums[mid+1] nums[mid]=nums[mid+1]不成立,故单独出现数字在左区间,更新 r i g h t = m i d right=mid right=mid;
- Step4:否则,说明 m i d mid mid在单独出现数字的右侧,故单独出现数字在左区间,此时 m i d mid mid可能为单独出现的数字,因此更新 r i g h t = m i d right=mid right=mid;
- Step5:当指针 l e f t left left小于 r i g h t right right时,重复步骤Step2_Step5;
- Step6:否则循环结束返回 n u m s [ l e f t ] nums[left] nums[left]。
该算法具体实现见Python实现方法五和Java实现。
3.代码实现
对于上述五个方法,除最后一个方法,前四个方法仅Python实现。
3.1 Python
方法一:遍历
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
n = len(nums)
for i in range(0, n, 2):
if i != n - 1:
if nums[i] != nums[i+1]:
return nums[i]
else:
continue
else:
return nums[i]
方法二:集合
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
return sum(set(nums)) * 2 - sum(nums)
方法三:二分查找(1)
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] != nums[mid-1] and nums[mid] != nums[mid+1]:
return nums[mid]
elif nums[mid] == nums[mid-1]:
if (mid - left) % 2 == 0:
right = mid
else:
left = mid + 1
else:
if (mid - left) % 2 == 0:
left = mid
else:
right = mid - 1
return nums[left]
方法四:二分查找(2)
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] == nums[mid ^ 1]:
# 如果mid为偶数,则mid与1的异或为mid+1
# 如果mid为奇数,则mid与1的异或为mid-1
left = mid + 1
else:
right = mid
return nums[left]
方法五:二分查找(3)
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
mid = mid if mid % 2 == 0 else mid - 1 # 也可以使用与运算统一
# mid -= mid & 1,当mid为偶数时,mid&1=0;当mid为奇数时,mid&1=1
if (nums[mid] != nums[mid+1]):
right = mid
else:
left = mid + 2
return nums[left]
3.2 Java
class Solution {
public int singleNonDuplicate(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right){
int mid = (left +right) / 2;
mid -= mid & 1;
if (nums[mid] != nums[mid+1]){
right = mid;
}else{left = mid + 2;}
}
return nums[left];
}
}
执行用时不必过于纠结,对比可以发现,对于python和java完全相同的编写,java的时间一般是优于python的;至于编写的代码的执行用时击败多少对手,执行用时和网络环境、当前提交代码人数等均有关系,可以尝试完全相同的代码多次执行用时也不是完全相同,只要确保自己代码的算法时间复杂度满足相应要求即可,也可以通过点击分布图查看其它coder的code。