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)空间复杂度中运行。

思路 1:根据特征二分

在唯一的数出现之前,连续相同的两个元素下标是偶奇,出现之后就是奇偶,根据这一特征来对整个数组进行二分查找。具体讲一下如何判断唯一数在 mid 指针的哪一边:

  • 如果 mid == 0,如果 nums[0] == nums[1],唯一数要去右边找,否则唯一数就是 mid
  • 如果 mid == nums.size()-1,如果 nums[nums.size()-2] == nums[nums.size()-1],唯一数要去左边找,否则唯一数就是 mid
  • 排除上面两种情况,mid 就在中间了,如果 mid 和两侧元素都不一样,就找到了
  • 其他情况得先看 mid 本身是奇数偶数再分类讨论

这个思路的问题在于判断的过程有点复杂了,所以代码中 checkPosition 函数写了那么多行,官方题解给出了思路 2,简洁很多。

时间复杂度: O ( log ⁡ n ) O(\log n) O(logn)
空间复杂度: O ( 1 ) O(1) O(1)

C++ 代码

class Solution {
public:
    // 在唯一的数出现之前,连续相同的两个元素下标是偶奇,出现之后就是奇偶

    // 查找index和唯一数的相对位置,重合返回0,唯一数在index左边返回-1,右边返回1
    int checkPosition(vector<int>& nums, int index) {
        int size = nums.size();
        // 特判index在最左侧的情况
        if (index == 0) {
            if (nums[0] == nums[1])
                return 1;
            else
                return 0;
        }
        // 特判index在最右侧的情况
        if (index == size - 1) {
            if (nums[size - 2] == nums[size - 1])
                return -1;
            else
                return 0;
        }
        // index在中间的情况
        // 如果和两侧的都不同,就找到了
        if (nums[index] != nums[index - 1] && nums[index] != nums[index + 1])
            return 0;
        // 否则根据和nums[index - 1]是否相同来判断唯一数在左侧还是右侧
        if (index % 2) {
            if (nums[index] == nums[index - 1])
                return -1;
            else
                return 1;
        } else {
            if (nums[index] == nums[index - 1])
                return 1;
            else
                return -1;
        }
    }
    
    int singleNonDuplicate(vector<int>& nums) {
        int size = nums.size(), left = 0, right = size - 1, mid;
        if (size == 1)
            return nums[0];
        while (left < right) {
            mid = left + (right - left) / 2;
            if (checkPosition(nums, mid) == 0)
                return nums[mid];
            else if (checkPosition(nums, mid) == 1)
                right = mid;
            else
                left = mid + 1;
        }
        return nums[left];
    }
};

思路 2:只对偶数下标进行二分搜索

容易证明,唯一数的下标一定是个偶数,并且数组长度一定是个奇数(所以最后一个下标是偶数)。所以只对偶数下标进行二分搜索即可。在唯一数右侧的偶数下标,所指的数和左侧的数相同,在唯一数左侧的偶数下标,所指的数和右侧的数相同,根据这一特点进行二分。

代码中二分的初始范围是 [0, (size - 1) / 2],这是所有偶数下标的数量,进行判断的时候将其乘以 2 就是实际的下标大小了。

C++ 代码

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int size = nums.size(), lo = 0, hi = (size - 1) / 2, mid;
        while (lo < hi) {
            mid = lo + (hi - lo) / 2;
            int realMid = mid * 2;
            if (realMid < size && nums[realMid] == nums[realMid + 1])
                lo = mid + 1;
            else if (realMid > 0 && nums[realMid] == nums[realMid - 1])
                hi = mid;
            else
                return nums[realMid];
        }
        return nums[lo * 2];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值