当前位置: 首页 > news >正文

位运算题目:寻找重复数

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
      • 进阶
  • 前言
  • 解法一
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法三
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:寻找重复数

出处:287. 寻找重复数

难度

6 级

题目描述

要求

给定一个包含 n + 1 \texttt{n} + \texttt{1} n+1 个整数的数组 nums \texttt{nums} nums,每个整数都在范围 [1, n] \texttt{[1, n]} [1, n] 内(包括 1 \texttt{1} 1 n \texttt{n} n)。

数组 nums \texttt{nums} nums 只有一个重复的整数,返回这个重复的数。

要求不修改数组 nums \texttt{nums} nums 且只用常量级的额外空间。

示例

示例 1:

输入: nums = [1,3,4,2,2] \texttt{nums = [1,3,4,2,2]} nums = [1,3,4,2,2]
输出: 2 \texttt{2} 2

示例 2:

输入: nums = [3,1,3,4,2] \texttt{nums = [3,1,3,4,2]} nums = [3,1,3,4,2]
输出: 3 \texttt{3} 3

示例 3:

输入: nums = [3,3,3,3,3] \texttt{nums = [3,3,3,3,3]} nums = [3,3,3,3,3]
输出: 3 \texttt{3} 3

数据范围

  • 1 ≤ n ≤ 10 5 \texttt{1} \le \texttt{n} \le \texttt{10}^\texttt{5} 1n105
  • nums.length = n + 1 \texttt{nums.length} = \texttt{n} + \texttt{1} nums.length=n+1
  • 1 ≤ nums[i] ≤ n \texttt{1} \le \texttt{nums[i]} \le \texttt{n} 1nums[i]n
  • nums \texttt{nums} nums 中的所有整数都只出现一次,除了一个整数出现两次或多次

进阶

  • 如何证明 nums \texttt{nums} nums 中至少存在一个重复的数字?
  • 你可以设计一个线性级时间复杂度的解决方案吗?

前言

由于数组的长度是 n + 1 n + 1 n+1,且最多包含 n n n 个不同的整数。根据抽屉原理(或鸽笼原理)可知,将数组的 n + 1 n + 1 n+1 个位置分配到 n n n 个不同的整数,至少有两个位置分配到同一个整数,即该整数在数组中重复。因此数组中至少存在一个重复的数字。

由于这道题要求不修改数组且空间复杂度是 O ( 1 ) O(1) O(1),因此排序、哈希表等解法都是不允许的,需要使用其他解法寻找重复的数字。

解法一

思路和算法

每个正整数的二进制表示中至少有一位是 1 1 1。对于二进制表示的每一位,分别考虑数组 nums \textit{nums} nums 中的所有整数在该位的 1 1 1 的次数 countArr \textit{countArr} countArr 和范围 [ 1 , n ] [1, n] [1,n] 中的所有整数在该位的 1 1 1 的次数 countNum \textit{countNum} countNum。假设整数 x x x 在数组 nums \textit{nums} nums 中出现超过一次,则 x x x 的二进制表示的每个 1 1 1 所在的位都满足 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum。理由如下。

  • 如果 x x x 出现两次,则范围 [ 1 , n ] [1, n] [1,n] 中的所有整数都在数组 nums \textit{nums} nums 中出现,因此对于 x x x 中等于 1 1 1 的每一位都有 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum

  • 如果 x x x 出现超过两次,则范围 [ 1 , n ] [1, n] [1,n] 中的部分整数不在数组 nums \textit{nums} nums 中出现,可以看成 x x x 替代了这部分整数。对于 x x x 中等于 1 1 1 的每一位,被替代的整数在相应位的值等于 0 0 0 1 1 1。替代之前有 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum,替代之后同样有 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum

除了 x x x 的二进制表示的每个 1 1 1 所在的位以外,其余位都不满足 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum。因此上述结论的逆命题也成立:假设所有满足 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum 的位组成的整数是 x x x,则 x x x 在数组 nums \textit{nums} nums 中出现超过一次。

根据上述分析,可以使用位运算寻找重复数。首先找到 n n n 的二进制表示的最高有效位,即最高位 1 1 1 所在的位数,然后依次遍历最低有效位到最高有效位,对于每一位计算 countArr \textit{countArr} countArr countNum \textit{countNum} countNum,如果 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum 则将该位加到重复数中。遍历结束之后即可得到重复数。

代码

class Solution {public int findDuplicate(int[] nums) {int n = nums.length - 1;int highBit = 0;int temp = n;while (temp != 0) {highBit = temp & (-temp);temp -= highBit;}int duplicate = 0;for (int i = 1; i <= highBit; i <<= 1) {int countArr = 0, countNum = 0;for (int num : nums) {int bit = num & i;if (bit != 0) {countArr++;}}for (int j = 1; j <= n; j++) {int bit = j & i;if (bit != 0) {countNum++;}}if (countArr > countNum) {duplicate += i;}}return duplicate;}
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 nums \textit{nums} nums 的长度减 1 1 1。需要遍历的二进制表示位数是 O ( log ⁡ n ) O(\log n) O(logn),对于每一位都需要 O ( n ) O(n) O(n) 的时间计算该位在重复数中是否等于 1 1 1,时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 空间复杂度: O ( 1 ) O(1) O(1)

解法二

思路和算法

假设有一个长度为 n + 1 n + 1 n+1 的数组 counts \textit{counts} counts,其中 counts [ i ] \textit{counts}[i] counts[i] 表示数组 nums \textit{nums} nums 中的不超过 i i i 的整数个数。以下用 x x x 表示重复数。

如果 x x x 出现两次,则当 i < x i < x i<x counts [ i ] = i \textit{counts}[i] = i counts[i]=i,当 i ≥ x i \ge x ix counts [ i ] = i + 1 > i \textit{counts}[i] = i + 1 > i counts[i]=i+1>i

如果 x x x 出现超过两次,则范围 [ 1 , n ] [1, n] [1,n] 中的部分整数不在数组 nums \textit{nums} nums 中出现,可以看成 x x x 替代了这部分整数,假设只有一个整数被替代,用 j j j 表示被替代的整数,分别考虑 j < x j < x j<x j > x j > x j>x 的情况。

  • 如果 j < x j < x j<x,则对于 i < x i < x i<x,不超过 i i i 的整数个数不变或减少,因此 counts [ i ] ≤ i \textit{counts}[i] \le i counts[i]i,对于 i ≥ x i \ge x ix,不超过 i i i 的整数个数不变,因此 counts [ i ] > i \textit{counts}[i] > i counts[i]>i

  • 如果 j > x j > x j>x,则对于 i < x i < x i<x,不超过 i i i 的整数个数不变,因此 counts [ i ] = i \textit{counts}[i] = i counts[i]=i,对于 i ≥ x i \ge x ix,不超过 i i i 的整数个数不变或增加,因此 counts [ i ] > i \textit{counts}[i] > i counts[i]>i

因此当 i < x i < x i<x counts [ i ] ≤ i \textit{counts}[i] \le i counts[i]i,当 i ≥ x i \ge x ix counts [ i ] > i \textit{counts}[i] > i counts[i]>i。如果被替代的整数有多个,该结论仍成立。

重复数 x x x 是使得 counts [ x ] > x \textit{counts}[x] > x counts[x]>x 成立的最小整数 x x x,可以使用二分查找的方法寻找重复数。

low \textit{low} low high \textit{high} high 分别表示二分查找的下界和上界。由于 x x x 在范围 [ 1 , n ] [1, n] [1,n] 中,因此初始时 low = 1 \textit{low} = 1 low=1 high = n \textit{high} = n high=n

每次查找时,取 mid \textit{mid} mid low \textit{low} low high \textit{high} high 的平均数向下取整,计算数组 nums \textit{nums} nums 中的不超过 mid \textit{mid} mid 的整数个数 counts [ mid ] \textit{counts}[\textit{mid}] counts[mid],执行如下操作。

  • 如果 counts [ mid ] ≤ mid \textit{counts}[\textit{mid}] \le \textit{mid} counts[mid]mid,则重复数 x x x 小于等于 mid \textit{mid} mid,因此在 [ low , mid ] [\textit{low}, \textit{mid}] [low,mid] 中继续查找。

  • 如果 counts [ mid ] > mid \textit{counts}[\textit{mid}] > \textit{mid} counts[mid]>mid,则重复数 x x x 大于 mid \textit{mid} mid,因此在 [ mid + 1 , high ] [\textit{mid} + 1, \textit{high}] [mid+1,high] 中继续查找。

low = high \textit{low} = \textit{high} low=high 时,查找结束,此时 low \textit{low} low 即为重复数 x x x

实现方面,并不需要显性创建数组 counts \textit{counts} counts,而是可以在二分查找的过程中根据当前的 mid \textit{mid} mid 遍历数组 nums \textit{nums} nums 计算不超过 mid \textit{mid} mid 的整数个数,因此空间复杂度是 O ( 1 ) O(1) O(1)

代码

class Solution {public int findDuplicate(int[] nums) {int n = nums.length - 1;int low = 1, high = n;while (low < high) {int mid = low + (high - low) / 2;int count = 0;for (int num : nums) {if (num <= mid) {count++;}}if (count > mid) {high = mid;} else {low = mid + 1;}}return low;}
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 nums \textit{nums} nums 的长度减 1 1 1。需要执行 O ( log ⁡ n ) O(\log n) O(logn) 次二分查找,每次二分查找需要 O ( n ) O(n) O(n) 的时间遍历数组 nums \textit{nums} nums 计算不超过特定值的整数个数,时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 空间复杂度: O ( 1 ) O(1) O(1)

解法三

思路和算法

将数组看成有向图,范围 [ 0 , n ] [0, n] [0,n] 中的每个整数是一个结点,对于 0 ≤ i ≤ n 0 \le i \le n 0in 的每个下标 i i i,存在一条从 i i i 指向 nums [ i ] \textit{nums}[i] nums[i] 的有向边。对于重复数 x x x,存在至少两条指向 x x x 的边,因此有向图中存在环。

这道题可以看成在有向图中寻找环的入口,可以使用「环形链表 II」的快慢指针做法。以下只说明使用快慢指针寻找重复数的做法,正确性证明见「环形链表 II 的题解」。

寻找重复数分成两步。

第一步是使用快慢指针遍历有向图寻找相遇点。

初始时,快指针和慢指针都位于整数 0 0 0。每次将快指针移动两步,慢指针移动一步,在至少移动一次的情况下,当快指针和慢指针相遇时,相遇的位置为相遇点。

第二步是将两个指针分别从起点和相遇点开始遍历有向图寻找重复数。

初始时,两个指针分别位于整数 0 0 0 和相遇点。每次将两个指针各移动一步,两个指针相遇的整数即为重复数。

代码

class Solution {public int findDuplicate(int[] nums) {int fast = 0, slow = 0;int meet = -1;while (meet < 0) {fast = nums[nums[fast]];slow = nums[slow];if (fast == slow) {meet = fast;}}int pointer1 = 0, pointer2 = meet;while (pointer1 != pointer2) {pointer1 = nums[pointer1];pointer2 = nums[pointer2];}return pointer1;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度减 1 1 1。快慢指针的时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( 1 ) O(1) O(1)

http://www.xdnf.cn/news/250201.html

相关文章:

  • 最长公共前缀(14)
  • 基于Koa实现的服务端渲染 ✅
  • 8.进程概念(四)
  • 为什么大模型偏爱Markdown
  • 操作系统(1)多线程
  • 【Machine Learning Q and AI 读书笔记】- 03 小样本学习
  • 数字智慧方案6178丨智慧医院医疗信息化建设之以评促建(61页PPT)(文末有下载方式)
  • 微型计算机串行通信实验三全解析:从原理到实践的探索之旅
  • 《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》章节思维导图
  • 【验证技能】文档要求和好文档注意点
  • Python实现简易博客系统
  • Linux——线程(3)线程同步
  • ✨从噪声到奇迹:扩散模型如何“想象“出世界
  • 本地服务器备份网站数据,本地服务器备份网站的操作步骤
  • 产品手册小程序开发制作方案
  • C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 17)
  • python自动化测试
  • 【业务领域】计算机网络基础知识
  • 基于预计技术研究加速因子:原理、应用场景及模型验证
  • socket-IO复用技术
  • 米酒的功能和优缺点
  • 范围for 和 万能引用
  • 【业务领域】电脑网卡是主板还是cpu(主板的网卡是什么意思)
  • 神经网络入门
  • 题解:CF1133E K Balanced Teams
  • 专题二十一:无线局域网——WLAN
  • VAO与VBO的相关操作
  • 【软件技能】Verdi使用技巧总结
  • TactileNet 利用 AI 生成触觉图形填补视障人士无障碍鸿沟
  • 文章记单词 | 第56篇(六级)