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

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

示例 1:

输入: [1,1,2,3,3,4,4,8,8]
输出: 2

示例 2:

输入: [3,3,7,7,10,11,11]
输出: 10

注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。

【思路】

这道题给我们了一个有序数组,说是所有的元素都出现了两次,除了一个元素,让我们找到这个元素。如果没有时间复杂度的限制,我们可以用多种方法来做,最straightforward的解法就是用个双指针,每次检验两个,就能找出落单的。也可以将所有数字亦或起来,相同的数字都会亦或成0,剩下就是那个落单的数字。那么由于有了时间复杂度的限制,需要为O(logn),而数组又是有序的,不难想到要用二分搜索法来做。二分搜索法的难点在于折半了以后,如何判断将要去哪个分支继续搜索,而这道题确实判断条件不明显,比如下面两个例子:

1  1  2  2  3

1  2  2  3  3

这两个例子初始化的时候left=0, right=4一样,mid算出来也一样为2,但是他们要去的方向不同,如何区分出来呢?

仔细观察找规律,如果mid和mid+1相等,我们可以先计算出右边和当前数字不同的数字的总个数,如果是偶数个,说明落单数左半边,反之则在右半边。有了这个规律就可以写代码了,为啥我们直接就能跟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];
    }
};

当然,也有另外一种巧妙的办法:

可以把数组中的元素两两一组编组,如0、1为一组,2、3为一组.....这样每次看Mid和它同组的小伙伴是否相等?(mid为奇数,则看mid和mid+1;mid为偶数,则看mid和mid-1)如果相等了,说明落单数在右边,如果不等,说明在左边.

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int l = 0 ;
        int r =nums.size()-1;
        while(l < r){
            int mid = l + (r - l)/2;
            if(nums[mid] == nums[mid ^ 1]) l = mid + 1;
            else r = mid;
        }
        return nums[l];
    }
};

猜你喜欢

转载自blog.csdn.net/zpznba/article/details/87886367