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

题目来源

leetcode

题目解析

在这里插入图片描述

题目解析

136. 只出现一次的数字的升级版

二分

看到"有序数组", “ O(log n)时间复杂度和 O(1)空间复杂度”,就应该想到“二分搜索法”

    public static int singleNonDuplicate(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right){
            int mid = left + (right - left) / 2;
            if (nums[mid] == nums[mid + 1]){  // 和u右边相等
                int c = mid - left;    //mid左边个数
                if (c%2 == 0){  // 偶数个
                    left = mid + 2;
                }else{
                    right = mid - 1;
                }
            }else if (nums[mid] == nums[mid - 1]){
                int c = right - mid;  // mid右边个数
                if (c%2 == 0){  // 偶数个,说明在左边
                    right = mid - 2;
                }else{ // 奇数个,说明在右边
                    left = mid + 1;
                }
            }else{  // 1,1,2,3,3, 不等于右边,也不等于右边,直接返回
                return nums[mid];
            }
        }

        return nums[left];
    }

时间复杂度:O(log n),在每次循环迭代中,我们将搜索空间减少了一半。
空间复杂度:O(1),仅用了常数空间
在这里插入图片描述
优化二分

二分搜索法的难点在于折半了以后,如何判断将要去哪个分支继续搜索,而这道题确实判断条件不明显,比如下面两个例子:

1 1 2 2 3

1 2 2 3 3

这两个例子初始化的时候left=0, right=4一样,mid算出来也一样为2,但是他们要去的方向不同,如何区分出来呢?仔细观察我们可以发现,如果当前数字出现两次的话,我们可以通过数组的长度和当前位置的关系,计算出右边和当前数字不同的数字的总个数,如果是偶数个,如果是偶数个,说明落单数左半边,反之则在右半边。有了这个规律就可以写代码了,为啥我们直接就能跟mid+1比呢,不怕越界吗?当然不会,因为left如何跟right相等,就不会进入循环,所以mid一定会比right小,一定会有mid+1存在。当然mid是有可能为0的,所以此时当mid和mid+1的数字不等时,我们直接返回mid的数字就可以了,参见代码如下:

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int left = 0, right = nums.size() - 1, n = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == nums[mid + 1]) {
                if ((n - 1 - mid) % 2 == 1) right = mid;
                else left = mid + 1;
            } else {
                if (mid == 0 || nums[mid] != nums[mid - 1]) return nums[mid];
                if ((n - 1 - mid) % 2 == 0) right = mid;
                else left = mid + 1;
            }
        }
        return nums[left];
    }
};

在这里插入图片描述
来源

解法二

为什么要亦或1呢,原来我们可以将坐标两两归为一对,比如0和1,2和3,4和5等等。而亦或1可以直接找到你的小伙伴,比如对于2,亦或1就是3,对于3,亦或1就是2。如果你和你的小伙伴相等了,说明落单数在右边,如果不等,说明在左边,这方法,太叼了有木有,参见代码如下:

    public static int singleNonDuplicate(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right){
            int mid = left + (right - left) / 2;
            if (nums[mid] == nums[mid ^ 1]){  // 中间一组成双成对,前面一定也成对,落单一定在右边
               left = mid + 1;
            }else{  // 中间那个落单了
                right = mid;
            }
        }

        return nums[left];
    }

下面这种方法其实跟上面很像,没有用亦或1,但是对mid进行了处理,强制使其成为小伙伴对儿中的第一个位置,然后跟另一个小伙伴比较大小,参见代码如下:

    public static int singleNonDuplicate(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right){
            int mid = left + (right - left) / 2;
            if (mid % 2 == 1){
                --mid;
            }
            if (nums[mid] == nums[mid + 1]){  // 中间一组成双成对,前面一定也成对,落单一定在右边
               left = mid + 2;
            }else{  // 中间那个落单了
                right = mid;
            }
        }

        return nums[left];
    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值