剑指 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. 先用二分法找到中间值, 
  2. 检查中间值是不是唯一值
    1. 是唯一值,返回此值
    2. 不是唯一值,确定唯一值中间值的左侧还是右侧, 缩小查找区间(这步比较难)

面向测试编程, 在写代码之前提前准备好测试用例, 有助于分析各种情况,找到思路.

  • 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 排序数组中只出现一次的数字的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer 40.知识迁移能力 数组中只出现一次的数字

剑指OFFER 数组中只出现一次的数字

剑指offer-数组中只出现一次的数字

剑指offer:数组中只出现一次的数字

《剑指Offer——数组中只出现一次的两个数字,数组中唯一只出现一次的数字》代码

剑指offer数组中只出现一次的数字