一道情人节的二分题

题目描述

有序数组中的单一元素
在这里插入图片描述

题目思路

  本题的关键在于意识到在单独的元素出现以前,由于数组是有序的,两两相同的元素都排在一起,观察下标可以发现:

1 1 2 2 3 4 4
0 1 2 3 4 5 6

n u m s [ y ] = = n u m s [ y + 1 ] ⇔ y 是 偶 数 n u m s [ y − 1 ] = = n u m s [ y ] ⇔ y 是 奇 数 nums[y] == nums[y + 1] \Leftrightarrow y是偶数\\ nums[y - 1] == nums[y] \Leftrightarrow y是奇数\\ nums[y]==nums[y+1]ynums[y1]==nums[y]y
  当单独的元素出现以后,有:
n u m s [ z ] = = n u m s [ z + 1 ] ⇔ z 是 奇 数 n u m s [ z − 1 ] = = n u m s [ z ] ⇔ z 是 偶 数 nums[z] == nums[z + 1] \Leftrightarrow z是奇数\\ nums[z - 1] == nums[z] \Leftrightarrow z是偶数\\ nums[z]==nums[z+1]znums[z1]==nums[z]z
  所以我们可以利用这个性质进行二分,找满足第一个性质区间的右端点或者找满足第二个性质区间的左端点。

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) 
    {
        int n = nums.size();
        int l = 0, r = n - 1;
        while (l < r)
        {
        	/*因为更新时是l = mid 所以需要加1*/
            int mid = (l + r + 1) >> 1;
            if ((mid & 1) == 0)
            {
                if (mid > 0 && nums[mid - 1] == nums[mid]) 
                	r = mid - 1;
                else l = mid;
            }
            else
            {
                if (mid + 1 < n && nums[mid] == nums[mid + 1]) 
                	r = mid - 1;
                else l = mid;
            }
        }
        return nums[l];
    }
};

  本题还可以进一步优化,思考一个位运算异或,若x是奇数,则x ^ 1 = x - 1,若x是偶数,则x ^ 1 = x,利用异或可以把两种情况归结到一起:

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) 
    {
        //本题的关键在于在单个元素x出现以前
        //若nums[y] == nums[y + 1] 则y必为偶数
        //在单个元素x出现以后 
        //若nums[z] == nums[z + 1] 则z必为奇数
        //可以利用这个性质二分
        //更进一步的 当mid是奇数时 mid ^ 1 = mid - 1
        //当mid是偶数时 mid ^ 1 = mid + 1
        //我们找满足nums[y] == nums[y + 1] <-> y为偶数
        //nums[y - 1] == nums[y] <-> y是奇数
        //的右边界
        //所以我们只要比较nums[mid] == nums[mid ^ 1]是否成立即可 不必分奇偶讨论
        int l = 0, r = nums.size() - 1;
        while (l < r)
        {
            int mid = (l + r) >> 1;
            /*若满足这个性质 则说明mid还在左区间内 为了找边界 让l = mid + 1*/
            /*这里不会越界 因为若mid等于0 异或1还是0 
            且mid = (l + r) / 2 <= r mid + 1 大于r的时候不会大于n - 1  
            */
            if (nums[mid] == nums[mid ^ 1]) l = mid + 1;
            /*否则说明mid不在左区间内 更新r为mid*/
            else r = mid;
        }
        return nums[l];
    }
};

  观察下标还能观察到:

1 1 2 2 3 4 4
0 1 2 3 4 5 6

  因为单独出现的数字的左边都是成对的相同出现的数,所以单独出现的数的下标一定是偶数,可以对偶数下标进行二分,保持mid是偶数的条件是mid -= mid & 1,mid是奇数时,mid & 1是1;mid是偶数时,mid & 1是0.
  第一个不满足性质nums[y] == nums[y + 1]的偶数下标即为答案。

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) 
    {
        int n = nums.size();
        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = (l + r) >> 1;
            mid -= (mid & 1);
            /*若满足条件 则让l往前走两步*/
            if (mid + 1 < n && nums[mid] == nums[mid + 1])
                l = mid + 2;
            /*若不满足 让r = mid 控制一定能扫到答案*/
            else r = mid;
        }
        return nums[r];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值