排序题目:错误的集合

文章目录

题目

标题和出处

标题:错误的集合

出处:645. 错误的集合

难度

3 级

题目描述

要求

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

给定一个整数数组 nums \texttt{nums} nums,代表集合发生错误后的结果。

找到出现两次的整数和丢失的整数,将它们以数组的形式返回。

示例

示例 1:

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

输出: [2,3] \texttt{[2,3]} [2,3]

示例 2:

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

输出: [1,2] \texttt{[1,2]} [1,2]

数据范围

  • 2 ≤ nums.length ≤ 10 4 \texttt{2} \le \texttt{nums.length} \le \texttt{10}^\texttt{4} 2≤nums.length≤104
  • 1 ≤ nums[i] ≤ 10 4 \texttt{1} \le \texttt{nums[i]} \le \texttt{10}^\texttt{4} 1≤nums[i]≤104

解法一

思路和算法

首先将数组 nums \textit{nums} nums 升序排序,然后遍历数组寻找重复的数字和丢失的数字。

如果所有数字都是正确的,则在排序之后,长度为 n n n 的数组中的每个下标 i i i 都满足 nums [ i ] = i + 1 \textit{nums}[i] = i + 1 nums[i]=i+1,当 i > 0 i > 0 i>0 时有 nums [ i ] − nums [ i − 1 ] = 1 \textit{nums}[i] - \textit{nums}[i - 1] = 1 nums[i]−nums[i−1]=1。如果规定 nums [ − 1 ] = 0 \textit{nums}[-1] = 0 nums[−1]=0,则对于每个下标都有 nums [ i ] − nums [ i − 1 ] = 1 \textit{nums}[i] - \textit{nums}[i - 1] = 1 nums[i]−nums[i−1]=1。因此,遍历数组并计算每一对相邻元素之差,即可找到重复的数字和丢失的数字。用 curr \textit{curr} curr 和 prev \textit{prev} prev 分别表示当前数字和上一个数字,初始时 prev = 0 \textit{prev} = 0 prev=0。

  • 如果 curr − prev = 0 \textit{curr} - \textit{prev} = 0 curr−prev=0,则 curr \textit{curr} curr 为重复的数字。

  • 如果 curr − prev > 1 \textit{curr} - \textit{prev} > 1 curr−prev>1,则 curr \textit{curr} curr 和 prev \textit{prev} prev 之间的数字为丢失的数字,即 curr − 1 \textit{curr} - 1 curr−1 为丢失的数字。

重复的数字一定在数组中出现两次,因此遍历数组一定可以找到重复的数字。当丢失的数字不是 n n n 时,遍历数组可以找到丢失的数字(包括丢失的数字是 1 1 1 也可以找到,因为 prev \textit{prev} prev 的初始值是 0 0 0),但是当丢失的数字是 n n n 时,数组中不存在相邻元素之差等于 2 2 2,因此在遍历结束之后需要根据 nums [ n − 1 ] \textit{nums}[n - 1] nums[n−1] 的值判断丢失的数字是否是 n n n,当 nums [ n − 1 ] ≠ n \textit{nums}[n - 1] \ne n nums[n−1]=n 时,丢失的数字是 n n n。

代码

java 复制代码
class Solution {
    public int[] findErrorNums(int[] nums) {
        int[] errorNums = new int[2];
        Arrays.sort(nums);
        int n = nums.length;
        int prev = 0;
        for (int i = 0; i < n; i++) {
            int curr = nums[i];
            int difference = curr - prev;
            if (difference == 0) {
                errorNums[0] = curr;
            } else if (difference != 1) {
                errorNums[1] = curr - 1;
            }
            prev = curr;
        }
        if (nums[n - 1] != n) {
            errorNums[1] = n;
        }
        return errorNums;
    }
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间,遍历数组需要 O ( n ) O(n) O(n) 的时间,总时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)。

  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( log ⁡ n ) O(\log n) O(logn) 的递归调用栈空间。

解法二

思路和算法

为了找到丢失的数字,可以使用哈希表记录数组中每个数字的出现次数,然后寻找重复的数字和丢失的数字。

由于所有的数字都在范围 [ 1 , n ] [1, n] [1,n] 内,因此可以使用长度为 n + 1 n + 1 n+1 的数组代替哈希表记录每个数字的出现次数。得到每个数字的出现次数之后,依次遍历范围 [ 1 , n ] [1, n] [1,n] 内的每个数字,出现 2 2 2 次的数字是重复的数字,出现 0 0 0 次的数字是丢失的数字。

代码

java 复制代码
class Solution {
    public int[] findErrorNums(int[] nums) {
        int[] errorNums = new int[2];
        int n = nums.length;
        int[] counts = new int[n + 1];
        for (int num : nums) {
            counts[num]++;
        }
        for (int i = 1; i <= n; i++) {
            if (counts[i] == 2) {
                errorNums[0] = i;
            } else if (counts[i] == 0) {
                errorNums[1] = i;
            }
        }
        return errorNums;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次获得数组中的所有数字的出现次数,然后需要遍历范围 [ 1 , n ] [1, n] [1,n] 内的每个数字寻找丢失的数字。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。哈希表需要 O ( n ) O(n) O(n) 的空间。

解法三

思路和算法

可以使用原地哈希将空间复杂度降低到 O ( 1 ) O(1) O(1),并保持时间复杂度 O ( n ) O(n) O(n)。

原地哈希的思想是将每个数字都交换到正确的下标位置。如果所有数字都是正确的,则在经过原地哈希交换之后,对于每个下标 i i i 都有 nums [ i ] = i + 1 \textit{nums}[i] = i + 1 nums[i]=i+1。假设重复的数字是 j j j,丢失的数字是 k k k,则在经过原地哈希交换之后, nums [ k − 1 ] = j \textit{nums}[k - 1] = j nums[k−1]=j,数组中其余的每个下标处的元素都是正确的。

具体做法是,对于数组 nums \textit{nums} nums 的每个下标 i i i,如果 nums [ i ] ≠ i + 1 \textit{nums}[i] \ne i + 1 nums[i]=i+1 且 nums [ i ] ≠ nums [ nums [ i ] − 1 ] \textit{nums}[i] \ne \textit{nums}[\textit{nums}[i] - 1] nums[i]=nums[nums[i]−1],则下标 nums [ i − 1 ] \textit{nums}[i - 1] nums[i−1] 与下标 i i i 处的元素不相等,将下标 nums [ i − 1 ] \textit{nums}[i - 1] nums[i−1] 与下标 i i i 处的元素交换,交换之后 nums [ i − 1 ] = i \textit{nums}[i - 1] = i nums[i−1]=i,重复该过程直到 nums [ i ] = i + 1 \textit{nums}[i] = i + 1 nums[i]=i+1 或 nums [ i ] = nums [ nums [ i ] − 1 ] \textit{nums}[i] = \textit{nums}[\textit{nums}[i] - 1] nums[i]=nums[nums[i]−1],此时下标 i i i 处的元素无法继续交换。

当每个下标的交换操作都结束之后,遍历数组 nums \textit{nums} nums。如果遇到 nums [ i ] ≠ i + 1 \textit{nums}[i] \ne i + 1 nums[i]=i+1,则 nums [ i ] \textit{nums}[i] nums[i] 是重复的数字, i + 1 i + 1 i+1 是丢失的数字。

代码

java 复制代码
class Solution {
    public int[] findErrorNums(int[] nums) {
        int[] errorNums = new int[2];
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            while (nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) {
                swap(nums, nums[i] - 1, i);
            }
        }
        for (int i = 0; i < n; i++) {
            if (nums[i] != i + 1) {
                errorNums[0] = nums[i];
                errorNums[1] = i + 1;
                break;
            }
        }
        return errorNums;
    }

    public void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次,每次会将一个元素交换到正确的下标位置,因此每个元素的访问次数是常数次,时间复杂度是 O ( n ) O(n) O(n)。

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

相关推荐
七折困9 小时前
列表、数组排序总结:Collections.sort()、list.sort()、list.stream().sorted()、Arrays.sort()
java·集合·数组·排序
小李飞刀李寻欢1 个月前
NLP排序中的基础模型
人工智能·自然语言处理·nlp·排序·排序模型
小李飞刀李寻欢1 个月前
用于NLP领域的排序模型最佳实践
人工智能·自然语言处理·大模型·transformer·torch·模型·排序
用手码出世界2 个月前
数据结构——八大排序
c语言·数据结构·算法·排序算法·排序
UestcXiye2 个月前
Leetcode3219. 切蛋糕的最小总开销 II
c++·leetcode·贪心·排序·数据结构与算法
bdawn2 个月前
Python中的sorted()与list.sort():深入解析它们的效率差异
开发语言·python·list·排序·效率·sorted·list.sort
炫酷的伊莉娜2 个月前
【错题集】ruby 和薯条(排序 + 二分 / 双指针)
数据结构·算法·二分·排序·双指针
伟大的车尔尼2 个月前
排序题目:最小时间差
排序
伟大的车尔尼3 个月前
排序题目:多数元素 II
排序