# 【Day 22】错误的集合

# 题目描述

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

给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

示例 1:

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

给定数组的长度范围是 [2, 10000]。
给定的数组是无序的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/set-mismatch
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

# 我的回答

# 解法一

# 时间复杂度 O(2n) 两个循环

# 空间复杂度 O(n) 创建了一个新的空间

      var findErrorNums = function (nums) {
        if (nums === null || !nums.length) return;
        let map = new Map();
        let rest = [];
        for (let i = 0; i < nums.length; i++) {
          map.has(nums[i]) ? rest.push(nums[i]) : map.set(nums[i], i);
        }
        const n = nums.length;
        for (let j = 1; j <= n; j++) {
          if (!map.has(j)) { rest.push(j); continue; }
        }
        return rest;
      };

# 参考回答

# #645 错误的集合

# 前置知识

  • 哈希表
  • 求和公式

# 思路

读完了题,大概题目是要干什么了,就是有 1-n 这么一个 n 个元素的集合,但是有一个元素重复了,这样就导致有一个元素消失了,我们就是要找出重复的元素和消失的元素是谁。

写代码如果不能立刻想到最优解,那么先按最简单的暴力去写看看能不能过,然后再考虑如何去优化就行,没必要上来就写最优解。

回到该题,既然元素是啥都知道了,我们就挨个元素判断就行,这样不就找出来谁是多的谁是少的了嘛,上代码

public int[] findErrorNums(int[] nums) {

    // 简单防御编程
    if (nums == null || nums.length <= 1)
        return new int[]{};

    int duplicate = -1, miss = -1;
    // 从1到N挨个判断
    for (int i = 1; i <= nums.length; i++) {

        int count = 0;

        for (int j = 0; j < nums.length; j++)
            if (nums[j] == i)
                count++;
        // 重复
        if (count == 2)
            duplicate = i;
        // 丢失
        else if (count == 0)
            miss = i;
    }
    return new int[] {duplicate, miss};
}

看看上面代码,明显时间复杂度是 N 的平方级,那么我们能不能试试优化一下呢?很明显,当我们找到了 duplicate 和 miss 就没必要继续遍历后面的元素了,这样虽然不能真正优化时间复杂度,但是实现了剪枝,同样效果不错!

public int[] findErrorNums(int[] nums) {

    // 简单防御编程
    if (nums == null || nums.length <= 1)
        return new int[]{};

    int duplicate = -1, miss = -1;
    // 从1到N挨个判断
    for (int i = 1; i <= nums.length; i++) {

        int count = 0;

        for (int j = 0; j < nums.length; j++)
            if (nums[j] == i)
                count++;
        // 重复
        if (count == 2)
            duplicate = i;
        // 丢失
        else if (count == 0)
            miss = i;

        // 剪枝
        if (duplicate > 0 && miss > 0)
            break;
    }
    return new int[] {duplicate, miss};
}

复杂度还是平方级,我们再进一步考虑,元素是 1-N,那么我们自然可以用高斯求和公式来求出所有元素和,遍历一遍数组也能知道当前元素和,那么关键点就是能否用尽量少的时间找到这个重复元素,这样不难想到用哈希表来存入已经遍历的元素!找到了重复元素,知道了原本元素的和,又知道了当前元素的和,答案不就出来了嘛!

public int[] findErrorNums(int[] nums) {

    // 简单防御编程
    if (nums == null || nums.length <= 1)
        return new int[]{};

    Set<Integer> set = new HashSet<>();
    int repeat = 0, lost = 0, sum = 0, len = nums.length;

    for (int i = 0; i < len; i++) {
        sum += nums[i];
        if (!set.add(nums[i]))
            repeat = nums[i];
    }
    // 自然数求和
    lost = (len * (len + 1)) / 2 - sum + repeat;

    return new int[]{repeat, lost};
}

至此,我们就将时间复杂度降到了 O(N),如果大家还有啥更有趣的解题思路欢迎提交至 issue 下。

ps:希望大家刷完该题之后尽量再手动实现输入输出,相信我,只有好处没有坏处!

比如,如下输入格式:

5
1 2 2 3 5
Last Updated: 12/22/2022, 9:53:26 AM