找到所有数组中消失的数字

题目:

给你一个含 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]

提示:

  • n == nums.length
  • 1 <= n <= 105
  • 1 <= nums[i] <= n

暴力算法解题思路

暴力算法的思路是遍历整数范围 [1, n],对于每一个整数检查其是否在数组 nums中出现。如果某个整数没有出现在 nums中,就将其添加到结果数组中。

具体步骤如下:

  1. 创建一个布尔数组 present,用来标记范围 [1, n] 内的整数是否出现在 nums中。初始时,所有元素都设为 false。

  2. 第一次遍历 nums数组,将出现的元素在 present数组中对应位置设为 true。

  3. 第二次遍历 present数组,找到所有值为 false的索引,即为没有出现在 nums中的整数。

  4. 将这些整数添加到结果数组中并返回。

复杂度分析

  • 时间复杂度:O(n)。需要两次遍历数组 nums,以及一次遍历长度为 n 的 present数组。这三次遍历的总时间复杂度为 O(n)。

  • 空间复杂度:O(n)。额外使用了一个长度为 n 的布尔数组 present。

暴力算法实现示例(Java):

import java.util.ArrayList;
import java.util.List;
​
public class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> result = new ArrayList<>();
        int n = nums.length;
        
        // Step 1: 创建一个boolean类型的数组,因为数组下标从0开始
        //所以我们创建数组长度为n+1  这样下标从0~n
        boolean[] present = new boolean[n + 1];
        
        // Step 2: 遍历nums数组,将出现的元素在 present数组中对应位置设为 true。
        for (int num : nums) {
            present[num] = true;
        }
        
        // Step 3: 找到从1~n没有出现的数字
        for (int i = 1; i <= n; i++) {
            if (!present[i]) {
                result.add(i);
            }
        }
        
        return result;
    }
    
    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums1 = {4, 3, 2, 7, 8, 2, 3, 1};
        System.out.println(solution.findDisappearedNumbers(nums1)); 
        
        int[] nums2 = {1, 1};
        System.out.println(solution.findDisappearedNumbers(nums2)); 
    }
}

进阶:

你能在不使用额外空间且时间复杂度为 O(n) 的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。

不使用额外的空间,那就只能利用数组本身的特性

方法一:将每个元素-1,结合下标处理

转换数组元素

  • 对于数组中的每个元素 nums[i],将其减去 1,得到 index = nums[i] - 1。这个 index就是数组中的下标。

标记出现情况

  • 如果 nums[index]大于 0,则将其乘以 -1(即取负),表示在原位置 index上的元素已经出现过。

  • 如果 nums[index]已经是负数(即已经被标记过出现),则不做处理。

找出未出现的元素

  • 第二次遍历数组 nums,找出所有正数的下标 i,表示原数组中缺失的元素为 i + 1。

示例(Java)

import java.util.ArrayList;
import java.util.List;
​
public class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> result = new ArrayList<>();
        int n = nums.length;
        
        for (int i = 0; i < n; i++) {
            int index = Math.abs(nums[i]) - 1; 
            /*当数组中存在重复元素时,例如数组中有两个元素值相同的情况,例如 [1, 2, 2]。在第一次遍历时,第一个 2可能会将下标为 1的元素标记为负数,然后第二个 2又会尝试标记同样的位置,这样做会使得原本出现的元素被误判为没有出现过,所以先判断*/
            if (nums[index] > 0) {
                nums[index] = -nums[index];
            }
        }
        
        
        for (int i = 0; i < n; i++) {
            if (nums[i] > 0) {
                result.add(i + 1);
            }
        }
        
        return result;
    }
    
    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums1 = {4, 3, 2, 7, 8, 2, 3, 1};
        System.out.println(solution.findDisappearedNumbers(nums1)); // Output: [5, 6]
        
        int[] nums2 = {1, 1};
        System.out.println(solution.findDisappearedNumbers(nums2)); // Output: [2]
    }
}

复杂度分析

  • 时间复杂度:O(n),两次遍历数组。

  • 空间复杂度:O(1),除了输出结果外,没有使用额外空间。

这种方法利用了原数组的特性,通过改变数组元素的正负来标记出现情况,从而在常数空间内找出缺失的元素,非常高效。

方法二:对每个元素-1进行取模运算后得到下标

  • 初始化和第一次标记

    • length 是数组 nums 的长度,即元素个数。
    • 对于数组中的每个元素 nums[i],计算出应该标记的位置 id。这里使用 (nums[i] - 1) % length 是为了将元素值转换成对应的下标(从0开始),这样可以在不修改原始数组的情况下进行标记。
    • 将 nums[id] 的值加上 length。这一步的目的是利用数组本身来标记数字的出现:如果数字 i+1 存在于 nums 中,那么 nums[i] 的值会大于 length
  • 第二次遍历找到缺失的数字

    • 创建一个空的 ArrayList 用来存放缺失的数字。
    • 再次遍历数组 nums,对于每个下标 i,如果 nums[i] 小于等于 length,则说明 i+1 这个数字在 nums 中没有出现,因为如果出现过,nums[i] 的值会被设置为大于 length

示例(Java)

import java.util.ArrayList;
import java.util.List;
​
public class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
       int n = nums.length;
        //利用下标
        for (int i = 0; i < n; i++) {
            //对n取模还原出原来的值对应的下标
            int id = (nums[i] - 1 )% n;
            //然后将该下标对应的值 + n
            nums[id] += n;
        }
        List<Integer> result = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if(nums[i] <= n){
                result.add(i+1);
            }
        }

        return result;
    }
    
    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums1 = {4, 3, 2, 7, 8, 2, 3, 1};
        System.out.println(solution.findDisappearedNumbers(nums1)); 
        
        int[] nums2 = {1, 1};
        System.out.println(solution.findDisappearedNumbers(nums2)); 
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组 nums 的长度。算法需要对 nums 进行两次线性扫描:一次是标记操作,一次是找到缺失数字的操作,因此总体时间复杂度为 O(2n),即 O(n)。

  • 空间复杂度:O(1)。除了返回的结果数组外,算法没有使用额外的空间,所有操作均在原数组上进行修改和计算。

这种方法利用了原数组的特性,通过改变数组元素的大小,又通过取模运算还原出原来的值,最后通过大小来标记出现情况,从而在常数空间内找出缺失的元素,非常高效。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无知、508

你的鼓励实我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值