🚩传送门:https://leetcode-cn.com/problems/set-mismatch/

题目

集合 s 包含从 1n 的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,导致集合 丢失了一个数字 并且 有一个数字重复

给定一个数组 nums 代表了集合 S 发生错误后的结果。找出重复出现以及丢失的整数,以数组的形式返回。

示例 1:

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

示例 2:

输入:nums = [1,1] 输出:[1,2]

解题思路:排序

将数组排序之后,比较每对相邻的元素,如果相邻的两个元素相等,则该元素为重复的数字 。
寻找丢失的数字相对复杂,可能有以下两种情况:

  1. 如果丢失的数字大于 1 且小于 n,则存在相邻的两个元素的2,这两个元素之间的值为丢失的数字;
  2. 如果丢失的数字是 1n,则需要另外判断。

复杂度分析

时间复杂度:[LeetCode]Ar645. 错误的集合 - 图1,其中 [LeetCode]Ar645. 错误的集合 - 图2[LeetCode]Ar645. 错误的集合 - 图3 的长度。

排序需要[LeetCode]Ar645. 错误的集合 - 图4 ,遍历数组找到错误元素需要 [LeetCode]Ar645. 错误的集合 - 图5 的时间,因此总时间复杂度是[LeetCode]Ar645. 错误的集合 - 图6

空间复杂度:[LeetCode]Ar645. 错误的集合 - 图7[LeetCode]Ar645. 错误的集合 - 图8 ,其中 [LeetCode]Ar645. 错误的集合 - 图9[LeetCode]Ar645. 错误的集合 - 图10 的长度。

根据不同的排序需要[LeetCode]Ar645. 错误的集合 - 图11[LeetCode]Ar645. 错误的集合 - 图12 的空间。

😱我的代码

  1. class Solution {
  2. public int[] findErrorNums(int[] nums) {
  3. //1.定义错误数组
  4. int[] errorNums = new int[2];
  5. int n = nums.length;
  6. //2.将nums数组排序
  7. Arrays.sort(nums);
  8. //3.依次遍历排序后的nums数组
  9. for (int i = 1; i < n; i++) {
  10. //4.当前数等于上一个数,此乃重复数字
  11. if (nums[i]==nums[i-1]) {
  12. errorNums[0] = nums[i];
  13. }
  14. //5.差值为2,中间乃缺失数字
  15. else if (nums[i] - nums[i-1] > 1) {
  16. errorNums[1] = nums[i] - 1;
  17. }
  18. }
  19. //6.预防第一个不是1
  20. if(nums[0]!=1){
  21. errorNums[1] = 1;
  22. }
  23. //7.预防最后一个不是n
  24. if (nums[n - 1] != n) {
  25. errorNums[1] = n;
  26. }
  27. //8.返回答案数组
  28. return errorNums;
  29. }
  30. }

解题思路:哈希表

重复的数字在数组中出现 2 次,丢失的数字在数组中出现 0 次,其余的每个数字在数组中出现 1 次。因此可以使用哈希表记录每个元素在数组中出现的次数,然后遍历从 1n 的每个数字,分别找到出现 2 次和出现 0 次的数字,即为重复的数字和丢失的数字。

复杂度分析

时间复杂度:[LeetCode]Ar645. 错误的集合 - 图13,其中 [LeetCode]Ar645. 错误的集合 - 图14[LeetCode]Ar645. 错误的集合 - 图15 的长度。

需要遍历数组并填入哈希表,然后遍历从 1n 的每个数寻找错误的集合。

空间复杂度:[LeetCode]Ar645. 错误的集合 - 图16 ,其中 [LeetCode]Ar645. 错误的集合 - 图17[LeetCode]Ar645. 错误的集合 - 图18 的长度。

需要创建大小为 [LeetCode]Ar645. 错误的集合 - 图19 的哈希表。

官方代码

  1. class Solution {
  2. public int[] findErrorNums(int[] nums) {
  3. //1.返回答案数组
  4. int[] errorNums = new int[2];
  5. int n = nums.length;
  6. //2.查找的map集合
  7. Map<Integer, Integer> map = new HashMap<Integer, Integer>();
  8. //3.依次遍历存入map
  9. for (int num : nums) {
  10. map.put(num, map.getOrDefault(num, 0) + 1);
  11. }
  12. //4.依次遍历查找元素为0或2的元素
  13. for (int i = 1; i <= n; i++) {
  14. int count = map.getOrDefault(i, 0);
  15. //5.重复元素
  16. if (count == 2) {
  17. errorNums[0] = i;
  18. }
  19. //6.缺失元素
  20. else if (count == 0) {
  21. errorNums[1] = i;
  22. }
  23. }
  24. //7.返回结果集合
  25. return errorNums;
  26. }
  27. }

解题思路:位运算 [老熟人😗异或]

使用位运算,可以达到 [LeetCode]Ar645. 错误的集合 - 图20 的时间复杂度和 [LeetCode]Ar645. 错误的集合 - 图21 的空间复杂度。

异或运算:即两个输入相同时为0,不同则为1

a b a⊕b
0 0 0
0 1 1
1 0 1
1 1 0
  1. 归零律[LeetCode]Ar645. 错误的集合 - 图22
  2. 恒等律[LeetCode]Ar645. 错误的集合 - 图23
  3. 交换律[LeetCode]Ar645. 错误的集合 - 图24
  4. 结合律[LeetCode]Ar645. 错误的集合 - 图25
  5. 自反[LeetCode]Ar645. 错误的集合 - 图26

重复的数字在数组中出现 2 次,丢失的数字在数组中出现 0 次,其余的每个数字在数组中出现 1 次。
如果在数组的 n 个数字后面再添加从 1n 的每个数字,得到 2n 个数字,则在 2n 个数字中,重复的数字出现 3 次,丢失的数字出现 1 次,其余的每个数字出现 2 次。可以使用异或运算求解。

异或出来的结果必然是:重复数字( x ) ⊕ 缺失数字( y )

image.png

位运算的方法还是蛮有意思的,理解了“lowbit 为 x 和 y 的二进制表示中的最低不同位”,也就能理解这种解法了。
首先要知道的是 lowbit = xor & -xor ,由于位运算需要使用补码计算,因此尝试几个例子便可以得知,lowbit 就是 xor 中最低位的值。
例如(简化二进制为8位):
xor = 3, [0000 0011]补码
-xort = -3, [1111 1101]补码
即 lowbit = xor & -xor = 1, [0000 0001]补码, [0000 0001]原码

image.png

  • 解三真的牛批,说下自己看后的理解吧.
  • 首先分析lowbit方法,这个是用来获取一个数最低位1的,而lowbit(x^y),因为是异或运算,所以当x^y的某一位为1时,x和y一定是一个0和一个1,因此lowbit(x^y)就是获取到x和y不同位中最低的一个(其实获取任意一个都行,但最低位好获取)
  • 分组可以说是这道题的精髓了,按刚刚获取到的那一位分组,因为该位x和y一定是一个0一个1,所以可以确保他们被分到不同组.
  • 之后就是都知道的了,因为x和y出现奇数次,而其余出现偶数次,在进行完全部异或运算后两组最后的数一定是一个x一个y,然后遍历扫一遍就行.

2踩回复

👀解题思路:数学

快不快不知道,但是优雅

  1. 第一个元素:重复元素 = 当前数组和 - 去重后的数组和
  2. 第二个元素:缺失元素 = 1至n求和 - 去重后的数组和

    大佬代码

    1. class Solution {
    2. public int[] findErrorNums(int[] nums) {
    3. return new int[] {
    4. Arrays.stream(nums).sum() - Arrays.stream(nums).distinct().sum(),
    5. (1 + nums.length) * nums.length / 2 - Arrays.stream(nums).distinct().sum()
    6. };
    7. }
    8. }