leetcode----540.有序数组中的单一元素(异或和二分两种解法)

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

问题:给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。

请你找出并返回只出现一次的那个数。

你设计的解决方案必须满足 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

思路:暴力,异或,也能过,但是不符合时间复杂度要求

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int res = 0;
        for(int num : nums){
            res ^= num;
        }
        return res;
    }
}
  • 二分查找

根据区间中间节点与其左右节点是否相等,以及左右区间元素个数来判断,具体如下,mid为当前区间的中点

  • 若中间节点等于左相邻节点,即nums[mid] == nums[mid - 1],有两种情况:
    • 若区间[left, mid)中元素个数为偶数,则单一元素在左半区间,即[left, mid - 2];
    • 若区间[left, mid)中元素个数为奇数,则单一元素在右半区间,即[mid + 1, right];
  • 若中间节点等于右相邻节点,即nums[mid] == nums[mid + 1],也有两种情况,类似于上面这种情况
class Solution {
    public int singleNonDuplicate(int[] nums) {
        int left = 0, right = nums.length - 1;

        while(left < right){
            int mid = left + (int) (right - left) / 2;

            if(nums[mid] == nums[mid - 1]){
                //根据区间[left, mid) 的个数,若mid等于左边元素,且左半区间数字个数为偶数,则单独元素在左半区间
                if((mid - left) % 2 == 0){
                    right = mid - 2;
                } else {
                    left = mid + 1;
                }
            } else if (nums[mid] == nums[mid + 1]){
                if((right - mid)  % 2 == 0){
                    left = mid + 2;
                } else {
                    right = mid - 1;
                }
            } else {
                return nums[mid];
            }
        }
        return nums[right];
    }
}

在评论中看到一个容易理解,且代码简洁的思路

  • 唯一出现的元素一定是第奇数个元素,也就是索引为偶数的元素,在唯一出现的元素后的所有元素的两次出现顺序会由索引 偶数-奇数 变成 奇数-偶数。
  • 采用二分法,并确保需要判断的中间元素的索引为偶数,然后与后一位元素判断是否相等。
  • 当判断相等,则证明唯一出现的元素在中间元素后,否则在中间元素前。
class Solution {
    public int singleNonDuplicate(int[] nums) {
        int left = 0, right = nums.length - 1;

        while(left < right){
            int mid = left + (int) (right - left) / 2;

            if((mid & 1) == 1) {
                mid--;
            }
            
            if(nums[mid] == nums[mid + 1]){
                left = mid + 2;
            } else {
                right = mid;
            } 
        }
        return nums[left];
    }
}
  • 官方题解,代码中有注释,大体思路如下:

若nums[mid]为单一元素,则其左右区间的元素个数都是偶数,

简单来说,就是若单一元素的下标为x,
则对于x左边的下标,元素对的第一个元素下标肯定为偶数,第二个元素下标肯定为奇数
对于x右边的下标,元素对的第一个元素下标肯定为奇数,第二个元素下标肯定为偶数

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int left = 0, right = nums.length - 1;

        while(left < right){
            int mid = left + (int) (right - left) / 2;

            //若当前下标为偶数,若等于其右相邻元素,即当前下标为元素对中的第一个元素下标,则说明单一元素在当前下标的右半区间,否则在左半区间
            //若当前下标为奇数,若等于其左相邻元素,即当前下标为元素对中的第二个元素下标,则说明单一元素在当前下标的右半区间,否则在左半区间
            if(nums[mid] == nums[mid ^ 1]){
                left = mid + 1;
            } else {
                right = mid;
            } 
        }
        return nums[left];
    }
}

细节1

这里说下我对每次左右边界变化的理解
不论mid为奇数或者偶数,若下面满足条件,则肯定不会是单一元素下标,所以收缩左边界时排除了mid位置的元素
若没满足下面的条件,则有可能是单一元素,并不确定,所以下一个搜索区间需要包含当前mid位置的元素

细节2

当mid为偶数时,mid ^ 1 = mid + 1

当mid为奇数时,mid ^ 1 = mid - 1

整理思路,记录博客,以便复习。若有误,望指正~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值