缺失数字&数组中重复的数字

缺失的第一个正数

给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

示例 1:

输入: [1,2,0]
输出: 3

示例 2:

输入: [3,4,-1,1]
输出: 2

示例 3:

输入: [7,8,9,11,12]
输出: 1

方法一:哈希表
先将数组中出现的元素都加入到hash表中,在判断1~n+1是否在hash表中出现过,没有出现即为缺失的元素

public int firstMissingPositive(int[] nums) {
    Set set = new HashSet<>();
    for (int num : nums) {
        set.add(num);
    }
    for (int i = 1; i <= nums.length + 1; i++) {
        if (!set.contains(i)) {
            return i;
        }
    }
    return -1;//其实不可能达到
}

方法二:原地哈希

public int firstMissingPositive(int[] nums) {
    boolean flag = false;
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] == 1) {
            flag = true;//标记1出现过
        }
        if (nums[i] <= 0) {
            nums[i] = 1;//将非正数转为1
        }
    }
    if (!flag) {
        return 1;//如果1没有出现过,缺失的就是1
    }
    for (int num : nums) {
        num = Math.abs(num);
        if (num <= nums.length && nums[num - 1] > 0) {// nums[num - 1] > 0保证只标记一次,防止负号不被改变
            nums[num - 1] = -nums[num - 1];//将索引位置标记为出现过
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] > 0) {
            return i + 1;
        }
    }
    return nums.length + 1;//1~n都出现了
}

交换

/*
思路:使用座位交换法
     缺失的第一个整数是 [1, len + 1] 之间,
     遍历数组,将对应的数据填充到对应的位置上去,比如 1 就填充到下标0的位置, 2就填充到下表1的位置
     如果填充过程中, nums[i] < 1 && nums[i] > len,那么直接舍弃
     填充完成,再遍历一次数组,如果对应的 nums[i] != i + 1,那么这个 i + 1 就是缺失的第一个正数
     
     比如 nums = [7, 8, 9, 10, 11], len = 5
     我们发现数组中的元素都无法进行填充,直接舍弃跳过,
     那么最终遍历数组的时候,我们发现 nums[0] != 0 + 1,即第一个缺失的是 1 

     比如 nums = [3, 1, 2], len = 3
     填充过后,最终数组变成了 [1, 2, 3],每个元素都对应了自己的位置,那么第一个缺失的就是 len + 1 == 4
*/
public int firstMissingPositive(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        /*
        1.只有在 nums[i] 是 [1, len] 之间的数,并且不在自己应该待的位置,nums[i] != i + 1 ,
        2.并且它应该待的位置没有被同伴占有(即存在重复值占有)nums[nums[i] - 1] != nums[i] 的时候才进行交换
        以上两点可以概括为nums[nums[i] - 1] != nums[i],因为它也代表了元素不在对应的位置

        为什么使用 while ? 因为交换后,原本 i 位置的 nums[i] 已经交换到了别的地方,
        交换后到这里的新值不一定是适合这个位置的,因此需要重新进行判断交换
        如果使用 if,那么进行一次交换后,i +1 进入下一个循环,那么交换过来的新值就没有去找到它该有的位置
        */
        while (nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] != nums[i]) {
            int temp = nums[i];
            nums[i] = nums[temp - 1];
            nums[temp - 1] = temp;
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != i + 1) {
            return i + 1;
        }
    }
    return nums.length + 1;
}

缺失数字

给定一个包含 0, 1, 2, ..., nn 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。

示例 1:

输入: [3,0,1]
输出: 2

示例 2:

输入: [9,6,4,2,3,5,7,0,1]
输出: 8

方法一:哈希

public int missingNumber(int[] nums) {
    Set set = new HashSet<>();
    for (int num : nums) {
        set.add(num);
    }
    for (int i = 0; i <= nums.length + 1; i++) {
        if (!set.contains(i)) {
            return i;
        }
    }
    return -1;//其实不可能达到
}

方法二:排序

public int missingNumber(int[] nums) {
    Arrays.sort(nums);
    for (int i = 0; i < nums.length; i++) {
        if (i != nums[i]) {
            return i;
        }
    }
    return nums.length;
}

方法三:异或
我们知道数组中有 n 个数,并且缺失的数在 [0..n] 中。因此我们可以先得到 [0..n] 的异或值,再将结果对数组中的每一个数进行一次异或运算。未缺失的数在 [0..n] 和数组中各出现一次,因此异或后得到 0。而缺失的数字只在 [0..n] 中出现了一次,在数组中没有出现,因此最终的异或结果即为这个缺失的数字。

public int missingNumber(int[] nums) {
    int xor = nums.length;
    for (int i = 0; i < nums.length; i++) {
        xor ^= i ^ nums[i];
    }
    return xor;
}

方法四:和
用0~n之后减去数组元素之后得到缺失的数字

public int missingNumber(int[] nums) {
    int n = nums.length;
    int expectedSum = n * (n + 1) / 2;
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    return expectedSum - sum;
}

五:原地哈希

public int missingNumber(int[] nums) {
    if (nums[0] != 0) {//确保0出现在第一位
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) {
                nums[i] = nums[0];
                nums[0] = 0;
                break;
            }
        }
    }
    for (int i = 0; i < nums.length; i++) {
        int num = Math.abs(nums[i]);
        if (num < nums.length) {
            if (nums[num] > 0) {
                nums[num] = -nums[num];
            }
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] > 0) {
            return i;
        }
    }
    return nums.length;
}

后来想到的方法:交换

public int missingNumber(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        while (nums[i] < nums.length && nums[nums[i]] != nums[i]) {
            int temp = nums[nums[i]];
            nums[nums[i]] = nums[i];
            nums[i] = temp;
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != i) {
            return i;
        }
    }
    return nums.length;
}

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

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

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

示例:

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

方法一
先将应该出现的数字存入set中,再遍历数组,将出现的数字从set中移除,set中剩下的数字即为消失的数字

public List findDisappearedNumbers(int[] nums) {
    Set set = new HashSet<>();
    for (int i = 1; i <= nums.length; i++) {
        set.add(i);
    }
    for (int num : nums) {
        set.remove(num);
    }
    return new ArrayList<>(set);
}

方法二
遍历输入数组的每个元素一次
将把 |nums[i]|-1 索引位置的元素标记为负数。即 nums[∣nums[i]∣−1]×−1 。
然后遍历数组,若当前数组元素 nums[i] 为负数,说明我们在数组中存在数字 i+1

public List findDisappearedNumbers(int[] nums) {
    List ans = new ArrayList<>();
    for (int num : nums) {
        int index = Math.abs(num) - 1;
        if (nums[index] > 0) {
            nums[index] *= -1;
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] > 0) {
            ans.add(i + 1);
        }
    }
    return ans;
}

交换的方法

public List findDisappearedNumbers(int[] nums) {
    List res = new ArrayList<>();
    for (int i = 0; i < nums.length; i++) {
        while (nums[nums[i] - 1] != nums[i]) {
            int temp = nums[i];
            nums[i] = nums[nums[i] - 1];
            nums[temp - 1] = temp;
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != i + 1) {
            res.add(i + 1);
        }
    }
    return res;
}

数组中重复的数字

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 :

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

方法一:Set
遍历数组,在遍历过程中将遇到的元素存起来,如果发现当前遇到的元素已经存在过就返回

public int findRepeatNumber(int[] nums) {
    Set set = new HashSet();
    for (int i = 0; i < nums.length; i++) {
        if (set.contains(nums[i])) {
            return nums[i];
        }
        set.add(nums[i]);
    }
    return -1;
}

方法二:排序

public int findRepeatNumber(int[] nums) {
    Arrays.sort(nums);
    for (int i = 1; i < nums.length; i++) {
        if (nums[i] == nums[i - 1]) {
            return nums[i];
        }
    }
    return -1;
}

方法三:原地修改
由于数字都在0~n-1范围内,考虑num可以看做数组索引,遍历过程中将索引位置设为负数,如果下次索引位置为负数说明出现了相同的元素。由于存在0,需要特殊处理

public int findRepeatNumber(int[] nums) {
    int zeroCount = 0;
    for (int i = 0; i < nums.length; i++) {
        int index = Math.abs(nums[i]);
        if (nums[index] < 0) {
            return index;
        }
        if (nums[i] == 0) {
            zeroCount++;
        }
        if (zeroCount > 1) {
            return 0;
        }
        nums[index] = -nums[index];
    }
    return -1;
}

原地交换
遍历中,第一次遇到数字 x 时,将其交换至索引 x 处;而当第二次遇到数字 x 时,一定有 nums[x] = nums[x]=x ,此时即可得到一组重复数字

public int findRepeatNumber(int[] nums) {
    int i = 0;
    while (i < nums.length) {
        if (nums[i] == i) {
            i++;
            continue;
        }
        // 一定要在上一个if后,否则可能因为nums[i]=i而导致相等
        if (nums[i] == nums[nums[i]]) {
            return nums[i];
        }   
        int temp = nums[i];
        nums[i] = nums[temp];
        nums[temp] = temp;
    }
    return -1;
}

你可能感兴趣的:(缺失数字&数组中重复的数字)