剑指 Offer II 070 排序数组中只出现一次的数字
Posted 想名真难
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指 Offer II 070 排序数组中只出现一次的数字相关的知识,希望对你有一定的参考价值。
给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。
示例 1:
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: nums = [3,3,7,7,10,11,11]
输出: 10
方法一:直接遍历
从0开始遍历,每次递进2,如果第i个和第i+1个不相等, 那么第i个就是结果。
class Solution
func singleNonDuplicate(_ nums: [Int]) -> Int
for i in 0 ..< (nums.count/2)
if nums[2*i] == nums[2*i+1]
continue
else
return nums[2*i]
// 前面的遍历不包含最后一个元素,比如[1,1,2], 如果到这里那结果肯定是最后一个元素
return nums.last!
优点: 空间复杂度为O(1),简单粗暴,比较容易想到.
缺点: 只能针对数组是有序的, 并且需要O(N)的时间复杂度
方法二:字典计数
1.把数组中的值作为key记录到字典中, value为出现的次数,
2.遍历字典, 找出只出现1次的key
class Solution
func singleNonDuplicate(_ nums: [Int]) -> Int
var dic = [Int:Int]()
// 遍历数组, 把数字出现的次数记录到字典中
for num in nums
if let value = dic[num]
dic.updateValue(value+1 , forKey: num)
else
dic.updateValue(1 , forKey: num)
// 遍历字典,找出出现1次的数字
for (key,value) in dic
if value == 1
return key
return 0
优点是对排序不敏感, 无论是乱序还是排序后的结果, 都能找出来, 适用性强;
缺点是需要额外的一块空间O(N), 而且需要遍历2次, 时间复杂度也是O(N).
方法三:所有数组元素异或的结果就是那个唯一的元素;
异或有一个特点, 任何数和自身异或结果肯定为0.
那么就可以, 全量把数组中的元素异或一次, 只出现一次的元素就是异或的最终结果.
class Solution
func singleNonDuplicate(_ nums: [Int]) -> Int
var result = 0
// 遍历数组, 每个数字都执行异或操作
for num in nums
result ^= num
return result
优点: 对排序不敏感, 无论是乱序还是排序后的结果, 都能找出来, 适用性强; 不需要额外的空间, 空间复杂度为O(1);
缺点: 数组必须要遍历完, 时间复杂度为O(N)
方法四:二分模拟
假设只出现一次的元素位于下标 x,由于其余每个元素都出现两次,因此下标 x 的左边和右边都有偶数个元素,数组的长度必定是奇数。由于数组是有序的,因此数组中相同的元素一定相邻。由于下标 x 是相同元素的开始下标的奇偶性的分界,因此可以使用二分查找的方法寻找下标 x。
- 先用二分法找到中间值,
- 检查中间值是不是唯一值
- 是唯一值,返回此值
- 不是唯一值,确定唯一值中间值的左侧还是右侧, 缩小查找区间(这步比较难)
面向测试编程, 在写代码之前提前准备好测试用例, 有助于分析各种情况,找到思路.
- 1,1,2,2,3,3,4 (唯一值在最右侧)
- 1,2,2,3,3,4,4 (唯一值在最左侧)
- 1 (数组中只有一个元素)
- 1,1,2,3,3(唯一值恰好是中间值)
在做的过程中,还发现数组的个数会影响唯一值在区间左侧和区间右侧的判断逻辑, 需要在补充测试用例,
比如 1,2,2,3,3 与 1,2,2,3,3,4,4 对比可以发现, 中间值的奇偶会影响取区间范围
- 第一个的中间为2 与后一个值3, 不相等, 应该取左侧区间
- 第二个中间值为3 与后一个值3相等, 也应该取左侧区间
补充2个测试用例,
- 1,1,2,2,3,(唯一值在最右侧)
- 1,2,2,3,3 (唯一值在最左侧)
如此测试用例的各种case都已覆盖, 在写的过程中需要时刻带入不同的测试用例检验自己的写法
class Solution
func singleNonDuplicate(_ nums: [Int]) -> Int
var midIndex = 0
var left = 0
var right = nums.count - 1
while left <= right
// 找到最中间的值
midIndex = (left + right) / 2
let midValue = nums[midIndex]
// 中间值是不是唯一值
if isThisNum(nums: nums, index: midIndex)
return midValue
else
// 中间值不是唯一值, 根据不同的情况, 设置left,right, 缩小下次查找范围
if midIndex % 2 == 0 // 是偶数
// 当前值和后一个值一样,说明唯一值在右侧, 比如[1,1,2,2,3]
if nums[midIndex] == nums[midIndex + 1]
left = midIndex
else
// 当前值和后一个值不一样,说明唯一值在左侧, 比如[1,2,2,3,3]
right = midIndex - 1
else // 是奇数
// 当前值和后一个值一样,说明在左侧, 比如[1,2,2]
if nums[midIndex] == nums[midIndex + 1]
right = midIndex
else
// 当前值和后一个值不一样,说明在左侧, 比如[1,1,2]
left = midIndex + 1
return nums[midIndex]
// 判断这个下表对应的值,是不是那个唯一的数字
func isThisNum(nums: [Int], index: Int) -> Bool
if nums.count == 1
return true
var result = false
// 此数字为最后一个数字, 只判断与前一个数字的关系
if index == nums.count - 1
if nums[index] == nums[index - 1]
result = false
else
result = true
else if index == 0
// 此数字为第一个数字, 只判断与后一个数字的关系
if nums[index] == nums[index + 1]
result = false
else
result = true
else
// 此数字为中间的任意数字, 判断与前后的关系,只要有一个相等,就不是目标值
if nums[index] == nums[index + 1]
result = false
else if nums[index] == nums[index - 1]
result = false
else
result = true
return result
优点: 时间复杂度为O(N) ,空间复杂度为O(1)
缺点: 代码逻辑复杂,判断很多.需要考虑各种不同的异常case
以上是关于剑指 Offer II 070 排序数组中只出现一次的数字的主要内容,如果未能解决你的问题,请参考以下文章