540. 有序数组中的单一元素(中等)

1. 题目描述

题目中转:540. 有序数组中的单一元素

在这里插入图片描述

2.详细题解

    方法一:若不限定时间复杂度,则扫描遍历一遍即可找到仅出现一次的数,具体实现见Python实现方法一
   方法二:若不限定空间复杂度,借助集合实现,具体实现见Python实现方法二,基本原理为:对数组求集合,再对集合中所有数字的和乘以2,结果假定为 X X X,此时 X X X的结果包括所有出现两次的数字之和,且对单独出现的数字也计算了两次,再对数组求和结果假定为 Y Y Y X − Y X-Y XY的差值即为单独出现的数字。
   方法三——二分查找(根据子区间个数奇偶):限定的时间复杂度: 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[mid1]:
  •   如果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=mid1
  •   否则在右区间内,则更新 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 mid1元素相等),则单独出现的数字在右区间,否则在左区间;< 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=mid1,确保 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。

  • 51
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raykingl

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值