剑指 Offer II 070(力扣540):排序数组中只出现一次的数字(Java二分查找)

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

一、题目描述



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


你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1)空间复杂度


示例 1:

输入: nums = [1,1,2,3,3,4,4,8,8]

输出: 2


示例 2:

输入: nums =  [3,3,7,7,10,11,11]

输出: 10


提示:

  • 1 <= nums.length <= 105
  • 0 <= nums[i] <= 105


二、思路讲解



2.1 哈希表

     

找个数的题目,用HashMap可以通杀。使用哈希表保存每个数字出现的次数,然后找到值为1的即可。


2.2 一次循环


因为数组本身是有序的(即使的无序的也可以给他排序),所以相等的数字必然相邻,那么我们可以一遍循环,找到一个数字,他的左边和右边跟自己都不相等,这个数字就是只出现一次的数字。


时间复杂度:        O(N)

空间复杂度:        O(1)

       

2.3 二分查找


然而,题目中要求的时间复杂度为对数级。由经验可得,有序数组的查找基本上可以使用二分查找,因此我们取左指针low指向最左,右指针high指向最右,中点mid指向中间,可以注意到:  

     

1、当mid为偶数时:

——如果nums[mid]跟左边的数字相等,那么所求数字一定出现在mid的左边,可以直接令high = mid -1。例如:


                               1        1        2        3        3        4        4        5        5


                               low                                 mid                                   high


       ——如果nums[mid]跟右边数字相等,那么所求数字一定出现在mid右边。例如:


                               1        1        2        2        3        3        4        5        5


2、当mid为奇数时:


       ——如果nums[mid]跟左边数字相等,那么所求数字定出现在mid右边。例如:


                     1        1        2        2        3        3        4        4        5        6        6


                   low                                              mid                                            high


       ——如果nums[mid]跟右边数字相等,则相反。例如:


                     1        1        2        3        3        4        4        5        5        6        6


       这样我们就已经确定了二分查找的大致思路。


剩下的困难就是边界的判断问题了,我们一起看看代码里是怎么做的:

class Solution {
    public int singleNonDuplicate(int[] nums) {
        if(nums.length==1 || nums[0]!=nums[1]) {
            return nums[0];
        }
        int low = 0;
        int high = nums.length - 1;
        while(low <= high) {
            //这样写在任何情况下都不会溢出
            int mid = (high - low) / 2 + low; 
            //如果mid达到了边界,就给left或right一个不可能的值,这样可以简化边界的判断
            int left = (mid>0)? nums[mid-1] : -1;
            int right = (mid<nums.length-1)? nums[mid+1] : Integer.MAX_VALUE;
            //如果nums[mid]跟两边都不相等,说明我们找的就是他
            if(nums[mid]!=left && nums[mid]!=right) {
                return nums[mid];
            }
            if(mid%2 == 0) {
                if(nums[mid] == right) {
                    low = mid + 1;
                } else{
                    high = mid -1;
                }
            } else {
                if(nums[mid] == left) {
                    low = mid + 1;
                } else {
                    high = mid - 1;
                }
            }
        }
        return nums[low];
    }
}


时间复杂度:        O(logN)

空间复杂度:        O(1)


2.4 位运算


利用按位异或的性质,可以减少对mid判断奇偶性的步骤,减少代码量,算得上是奇技淫巧了:


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

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

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int low = 0, high = nums.length - 1;
        while (low < high) {
            int mid = (high - low) / 2 + low;
            if (nums[mid] == nums[mid ^ 1]) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return nums[low];
    }
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/skFtm2/solutions/1252765/pai-xu-shu-zu-zhong-zhi-chu-xian-yi-ci-d-jk8w/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


目录
打赏
0
0
0
0
178
分享
相关文章
|
13天前
|
《从头开始学java,一天一个知识点》之:数组入门:一维数组的定义与遍历
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。明日预告:《多维数组与常见操作》。 通过实例讲解数组的核心认知、趣味场景应用、企业级开发规范及优化技巧,帮助你快速掌握Java数组的精髓。
57 23
Java快速入门之数组、方法
### Java快速入门之数组与方法简介 #### 一、数组 数组是一种容器,用于存储同种数据类型的多个值。定义数组时需指定数据类型,如`int[]`只能存储整数。数组的初始化分为静态和动态两种: - **静态初始化**:直接指定元素,系统自动计算长度,如`int[] arr = {1, 2, 3};` - **动态初始化**:手动指定长度,系统给定默认值,如`int[] arr = new int[3];` 数组访问通过索引完成,索引从0开始,最大索引为`数组.length - 1`。遍历数组常用`for`循环。常见操作包括求和、找最值、统计特定条件元素等。
Java 复制数组
本文介绍了Java中数组的基础知识与常用操作,包括数组的概念、创建、访问元素、遍历、复制、排序和搜索等方法。同时详细讲解了数组的五种赋值方式,并通过代码示例演示了求总和平均值、最大最小值、升序降序排序及Arrays类的常用方法。内容深入浅出,适合初学者学习掌握Java数组的核心功能与应用场景。
Java数组:静态初始化与动态初始化详解
本文介绍了Java中数组的定义、特点及初始化方式。
131 12
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
7月前
|
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
83 6
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
170 2
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
122 1
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
7月前
|
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
106 7
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等