448. 找到所有数组中消失的数字 题解

题目描述:448. 找到所有数组中消失的数字 - 力扣(LeetCode)

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

示例 1:

输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]

示例 2:

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

方法一:使用哈希集合

思路:

  1. 初始化一个哈希集合。
  2. 遍历数组 nums,将所有出现的数字添加到哈希集合中。
  3. 遍历区间 [1, n],检查每个数字是否在哈希集合中,如果不在集合中,则将其加入结果数组中。
  4. 返回结果数组。

代码:

typedef struct
{
    int *data;
    int size;
} HashSet;

// 初始化哈希集合
HashSet* initHashSet()
{
    HashSet* set = (HashSet*)malloc(sizeof(HashSet));
    set->data = (int *)calloc(1000000, sizeof(int));  // 注意初始化一定要是0,开辟的空间要大一点否则会越界
    set->size = 0;
    return set;
}

// 将元素加入哈希集合
void addToHashSet(HashSet* set, int num)
{
    set->data[num] = 1;
    set->size++;
}

// 判断元素是否在哈希集合中
int isInHashSet(HashSet* set, int num)
{
    return set->data[num];
}

// 释放哈希开辟的内存
void freeHashSet(HashSet* set)
{
    free(set->data);
    free(set);
}

int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) {
    int* result = (int*)malloc(sizeof(int) * numsSize);
    *returnSize = 0;

    HashSet* set = initHashSet();

    for (int i = 0; i < numsSize; i++) {
        addToHashSet(set, nums[i]);
    }

    for (int i = 1; i <= numsSize; i++) {
        if (!isInHashSet(set, i)) {
            result[(*returnSize)++] = i;
        }
    }

    freeHashSet(set);

    return result;
}

方法二:原地修改数组

这种方法利用了数组的索引,将出现的数字在对应位置进行标记。

数组元素的绝对值作为下标,将对应位置的数据置为负数,比如 0 号位置是 3 ,则把 3 号位置的数据重置为负值,等到数组遍历重置完毕,只有缺失的这个数字对应的位置保留正数,其他出现过的数字位置都会是负数, 要注意不要重复设置负数,因为负负得正。

示例:
[2, 3, 3, 2, 4] 注意数组10个元素,值为[1-10], 但是访问下标应该在[0-9]之内,因此修改位置下标应该是值-1
0号元素是2,则将1号(2-1)位置置为对应负值 [2, -3, 3, 2, 4]
1号元素是3,则将2号(3-1)位置置为对应负值 [2, -3, -3, 2, 4]
2号元素是-3,绝对值为3,将2号位置为负值,但是2号位已经重置过,不需要重置,否则会变正数[2, -3, -3, 2, 4]
3号元素是-2,绝对值为2,将1号位置为负值,但是1号位已经重置过,不需要重置,否则会变正数[2, -3, -3, 2, 4]
4号元素是4,则将3号位置置为对应负值 [2, -3, -3, -2, 4]
遍历数组得到0,4两个位置的数据是大于0的,因为人家数值从1开始,因此+1后得到1, 5两个缺失的数字
 

步骤:

  1. 遍历数组 nums,对于每个数字 num,将索引为 num - 1 绝对值位置的数字变为负数,表示数字 num 出现过。
  2. 再次遍历数组,如果某个位置上的数字为正数,说明对应的索引加 1 没有在数组中出现过。
  3. 将没有被标记的索引加 1 加入结果数组中。

代码:

int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) {
    int* result = (int*)malloc(sizeof(int) * numsSize);
    *returnSize = 0;

    for (int i = 0; i < numsSize; i++) {
        int num = abs(nums[i]);
        if (nums[num - 1] > 0) {  //当值大于0时才变为负数,避免负负得正
            nums[num - 1] = -nums[num - 1];
        }
    }

    for (int i = 0; i < numsSize; i++) {
        if (nums[i] > 0) {
            result[(*returnSize)++] = i + 1;
        }
    }

    return result;
}

时间复杂度和空间复杂度分析:

方法一:使用哈希集合

时间复杂度:

  • 初始化哈希集合的时间复杂度为 O(1)。
  • 将所有数字添加到哈希集合的时间复杂度为 O(n),因为需要遍历整个数组。
  • 遍历区间 [1, n] 并检查是否在哈希集合中的时间复杂度为 O(n)。

综上所述,方法一的总时间复杂度为 O(n)。

空间复杂度:

  • 初始化哈希集合的空间复杂度为 O(n),因为哈希集合的大小与输入数组元素最大值相同。
  • 存储结果数组的空间复杂度为 O(n)。

综上所述,方法一的总空间复杂度为 O(n)。

方法二:原地修改数组

时间复杂度:

  • 第一次遍历数组并修改标记的时间复杂度为 O(n)。
  • 第二次遍历数组并寻找没有标记的数字的时间复杂度为 O(n)。

综上所述,方法二的总时间复杂度为 O(n)。

空间复杂度:

  • 不需要额外的空间来存储哈希集合,因此空间复杂度为 O(1)。

综上所述,方法二的总空间复杂度为 O(1)。

综合来看,两种方法的时间复杂度都是相同的,都为 O(n),但是方法一需要额外的哈希集合来存储数据,导致空间复杂度为 O(n),而方法二在原地修改数组,空间复杂度为 O(1)。因此,如果要求不使用额外的空间,方法二是更优的选择。


本次内容到此结束了!如果你觉得这篇博客对你有帮助的话 ,希望你能够给我点个赞,鼓励一下我。感谢感谢……

你可能感兴趣的:(算法,数据结构)