分析
具体题目
给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 O(log n)
时间复杂度和 O(1)
空间复杂度。
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2
输入: nums = [3,3,7,7,10,11,11]
输出: 10
本来是感觉平平无奇的一个题目,一开始是没有思路的,并且准备放弃的。看了看官方解题,没忍住看了眼评论区。好家伙,原来这不是找单身狗的吗?还有人情人节抽到了这道题来做。然后提起来了兴趣,看了力扣的分析,感觉又行了,特此记录。
原力扣上地址,大家可以自行查阅。
分析题目
给的数据范围和格式
- 1、自增(有序)
- 2、不存在不存在的情况(不存在无解的情况)
- 3、只会出现一个出现一次的数字,其他均为出现两次
要求
- 1、你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度
- 2、请你找出并返回只出现一次的那个数。(不是下标,但是和下标其实时一样的)
开始解题
思路
1、如果是 仅存在一个单个的话,数组长度为奇数
2、而且当前位置为某个存在的成对数字,num[idx] = num[idx+1],即左边应为偶数。
3、相对于右边某个(假设和上面的是一对的) 即 num[idx] = num[idx-1] 右边应为偶数。
4、如果上面不符的话 ,应适当缩小范围,来达到二分的效果。
看到的解题的一个小技巧,对这道题还是很适用的,不过具体实现的原理我还没有理解,先简单分析一下。
技巧
具体的就是一个数字 num ^ 1 按位异或1的话,会出现以下的情况
- 1、如果num为偶数的话 num ^ 1 = num + 1
- 2、如果num为奇数的话 num ^ 1 = num - 1
这个具体和我们的题目有什么相关呢?我们可以不用担心二分中mid的奇偶性了,因为我们只需要分析mid和他应该搭档的一对是否时一致的就行了,甚至不需要考虑数据的递增递减以及是否时有序的。
继续分析技巧
- 1、中间值为偶数情况下。我们现在假设又这么个数组索引
0 1 2 3 4 5 6 7 8 9 10
,暂时不考虑数组中的数据。我们按照二分来分析下,left = 0
,right = 10
,mid = 5
。继续分析,mid ^ 1 = mid - 1 = 4
,即当前位置的异或下标为5。我们假设mid的左边全都是一对一对的,我们可以直观看到0,1
2,3
4,5
,这些下标位置应是对应的。 - 2、mid为偶数情况同上
- 3、 小结论:如果
mid
和mid^1
这两个下标的数字相同,我们左边就全部都是一对一对的,即我们要找的索引不在mid左边,此时lef应变为mid+1。单数要注意当不相等的时候,我们不能保证mid位置不为想要的数组下标,即如果不等,right=mid。
具体实现
具体其实就是一个二分,在这就不赘述了。
public int singleNonDuplicate(int[] nums) {
int left = 0, right = nums.length - 1, mid = 0;
while (left < right) {
mid = left + (right - left) / 2;
if (nums[mid] == nums[mid ^ 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}
总结
怎么说呢,做到最后好像就是一个普通的二分罢了。开始的时候除了遍历确实没有其他的思路了,也是看了官方解读。第一次关于按位与哪里没有看明白,后来就想着看不明白就不明白了,记住结论就行。推理按位与那里确实时按照最笨的方法推测可行性的。