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

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

示例 1:

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

注意:

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

思路:

正确的思路1:
做题目一定要先自己手写一个大概思路,不然真正写代码的时候,人会傻的!
1、先对数组进行排序
2、循环遍历数组,当nums[i+1] == nums[i]的时候,就break,然后得到nums[i]就是重复的那个
3、先考虑一种特殊情况就是:[1,2,2,3,4,5],这个缺的是6,特殊写一个分支
4、然后继续循环从1开始,当nums[i] >nums[i-1]+1的时候,就返回nums[i-1]+1,是缺的那个
代码一:
public int[] findErrorNums(int[] nums) {
        Arrays.sort(nums);//先对数组进行排序
        int lack = 1; //定义缺少的值默认为1,默认缺的是第一个数:1,这个地方很巧妙
        int doubles = 0; //定义重复的数为0
        //这个考虑一种特殊情况就是:[1,2,2,3,4,5],这个缺的是6,特殊写一个分支,就是当缺的是最后一个时,单独考虑
        if(nums[nums.length-1] != nums.length){
            lack = nums.length;  
        }else {
            for (int i = 1; i < nums.length; i++) {
                    if(nums[i] >nums[i-1]+1){  //nums[i] >nums[i-1]+1这个条件很重要,就是[1,2,2,3,5],缺的是4,那5>3+1
                    lack = nums[i-1]+1;
                    break;
                }
            }
        }
        //求重复的数还是简单:当你后面的等于前面的就说明重复了,返回前面的nums[i]就好了
        for (int i = 0; i < nums.length; i++) {
            if(nums[i+1] == nums[i]){
                doubles = nums[i];
                break;
            }
        }
        return new int []{doubles,lack};
    }
简洁版本:
public class Solution {
    public int[] findErrorNums(int[] nums) {
        Arrays.sort(nums);
        int dup = -1, missing = 1;
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == nums[i - 1])
                dup = nums[i];
            else if (nums[i] > nums[i - 1] + 1)
                missing = nums[i - 1] + 1;
        }
        return new int[] {dup, nums[nums.length - 1] != nums.length ? nums.length : missing};
    }
}
我的错误思路:
我刚开始就是这个想法做的,但是在缺项那个条件那里卡住了,我想的是元素nums[i] != i+1,然后返回i+1,这是错误的想法,自己还是要多思考。
正确的思路2:暴力解法
直接的做法就是单独检查 1 到 n的所有数字。
在检查每个数字时都遍历整个nums 数组,检查当前数字在 nums 中是否出现了两次,或者一次都没有出现。
使用 dup 和 missing 记录重复数字和缺失数字。
public class Solution {
    public int[] findErrorNums(int[] nums) {
        int dup = -1, missing = -1;
        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)
                dup = i;
            else if (count == 0)
                missing = i;
        }
        return new int[] {dup, missing};
    }
}
正确的思路3:优化的暴力解法
在 方法一 中,即使已经找到了重复数字和缺失数字,依然会继续查找。但是根据问题描述,只有一个重复数字和缺失数字,因此一旦找到这两个数字,就可以提前结束查找过程。
public class Solution {
    public int[] findErrorNums(int[] nums) {
        int dup = -1, missing = -1;;
        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)
                dup = i;
            else if (count == 0)
                missing = i;
            if (dup > 0 && missing > 0)
                break;
        }
        return new int[] {dup, missing};
    }
}
正确的思路4:使用异或运算
思考一个简单的问题,一个长度为 n-1n−1 的数组包含 11 到 nn 中的 n-1n−1 个数字,有一个数字缺失,如何找出这个缺失的数字。首先使用 11 到 nn 的所有数字做异或运算,然后再使用数组中的所有数字与这个数字异或,最后得到的值就是缺失数字。因为数字与其本身做异或运算结果为 0,因此所有数字做完异或后,剩余的就是缺失的数字。

按照这个方法,将 numsnums 中所有的数字与 11 到 nn 的所有数字做异或运算,得到的结果为 x^y,x 和 y 分别表示 numsnums 中重复数字和缺失数字。

在异或结果 xorxor 的二进制中,值为 1 的位置表示 xx 和 yy 在该位置的值不同,值为 0 的位置表示 x 和 y 在该位置的值相同。我们称 xorxor 最右边比特位为 rightmostbitrightmostbit,且使该位置为 1。

根据 rightmostbitrightmostbit 不同将数组 numsnums 分为两部分。第一部分所有数字的 rightmostbitrightmostbit 为 1,第二部分所有数字的 rightmostbitrightmostbit 为 0。那么 x 和 y会被分配到不同的部分。此时问题就简化为最开始的简单问题。

根据 rightmostbitrightmostbit 的不同,将 11 到 nn 的所有元素分为两部分。

现在分别使用从 numsnums 中分出来 rightmostbitrightmostbit 为 1 的部分与 11 到 nn 中分出来 rightmostbitrightmostbit 为 1 的部分做异或。在结果中,相同的元素异或为 0,最终只会剩下重复数字或缺失数字,即 xx 或 yy。同理也对 rightmostbitrightmostbit 为 0 的部分异或。
最后遍历 numsnums,确定两个数字中哪个为重复数字,哪个为缺失数字。
public class Solution {
    public int[] findErrorNums(int[] nums) {
        int xor = 0, xor0 = 0, xor1 = 0;
        for (int n: nums)
            xor ^= n;
        for (int i = 1; i <= nums.length; i++)
            xor ^= i;
        int rightmostbit = xor & ~(xor - 1);
        for (int n: nums) {
            if ((n & rightmostbit) != 0)
                xor1 ^= n;
            else
                xor0 ^= n;
        }
        for (int i = 1; i <= nums.length; i++) {
            if ((i & rightmostbit) != 0)
                xor1 ^= i;
            else
                xor0 ^= i;
        }
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == xor0)
                return new int[]{xor0, xor1};
        }
        return new int[]{xor1, xor0};
    }
}

第二题:697. 数组的度
给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。
你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1:
输入: [1, 2, 2, 3, 1]
输出: 2
解释: 
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.
示例 2:
输入: [1,2,2,3,1,4,2]
输出: 6
注意:
nums.length 在1到50,000区间范围内。
nums[i] 是一个在0到49,999范围内的整数。

思路:

具有度数 d 的数组必须有一些元素 x 出现 d 次。如果某些子数组具有相同的度数,那么某些元素 x (出现 d 次)。最短的子数组是将从 x 的第一次出现到最后一次出现的数组。
对于给定数组中的每个元素,让我们知道 left 是它第一次出现的索引; right 是它最后一次出现的索引。例如,当 nums = [1,2,3,2,5] 时,left[2] = 1 和 right[2] = 3。
然后,对于出现次数最多的每个元素 x,right[x] - left[x] + 1 将是我们的候选答案,我们将取这些候选的最小值。

具体步骤:

第一:先找到出现频数最大的那个数x,求出度为?
第二:找出每个元素第一次出现的下标left
第三:找到每个元素最后出现的下标right
第四:结果为right-left+1

代码:

class Solution {
    public int findShortestSubArray(int[] nums) {
        // 如果数组是一个空数组或者数组不空,但是里面没有任何元素,那就返回0
        if(nums == null || nums.length == 0){
            return 0;
        }
        // 1、如果数组就1个元素,数组的度就是1,
        // 2、包含这个度1的数组长度的最小值,这个数组长度本身了,就是这个1,因此返回1
        if(nums.length == 1){
            return 1;
        }
        // map1用来统计数组中每个元素出现的次数
        // 比如题中:1 -> 2 (数组中1出现了2次)
        //          2 -> 2 (数组中2出现了2次)
        //          3 -> 1 (数组中3出现了1次)
        Map<Integer, Integer> map1 = new HashMap<>();


        // map2用来记录数组中元素第一次出现时的位置(索引)
        // 比如题中:1 -> 0 (数组中1第一次出现的位置是0)
        //          2 -> 1 (数组中2第一次出现的位置是1)
        //          3 -> 3 (数组中3第一次出现的位置是3)
        Map<Integer, Integer> map2 = new HashMap<>();


        // map3用来记录数组相同元素,第一次出现与最后一次出现之间的长度
        // 比如题中:元素1第一次出现的位置是0,最后一次出现的位置是4,它们之间的长度 = 5
        //          元素2第一次出现的位置是1,最后一次出现的位置是2,它们之间的长度 = 3
        //          元素3第一次出现的位置是3,最后一次出现的位置是3,它们之间的长度 = 1
        Map<Integer, Integer> map3 = new HashMap<>();


        for(int i = 0; i < nums.length; i++){
            // map1用来统计数组中每个元素出现的次数,key = 数组元素, value = 出现的次数
            map1.put(nums[i], map1.getOrDefault(nums[i],0) + 1);
            // map2用来记录数组中元素第一次出现时的位置(索引),key = 数组元素, value = 第一次出现的位置(索引)
            if(!map2.containsKey(nums[i])){
                map2.put(nums[i],i);
            }
            // map3用来记录数组相同元素,第一次出现与最后一次出现之间的长度,key = 数组元素, value = 第一次出现与最后一次出现之间的长度
            // 数组元素第一次出现的位置保存在map2中,可以从里面拿出来
            map3.put(nums[i],i - map2.get(nums[i]) + 1);
        }
        // maxCount用来记录数组的度
        int maxCount = 0;
        //  map1中存了数组元素的次数,将出现次数的最大值取出来,放到maxCount中
        for(int num : map1.values()){
            maxCount = Math.max(maxCount, num);
        }
        // minLength用来记录最终的答案,保存最短连续子数组的长度,初始化为最大值
        // minLength是用来保存最小值的,将其初始化为最大值,才能不断更新,得到更小的,再更新得到更小的,最终得到的是最小的
        int minLength = Integer.MAX_VALUE;
        for(int key : map1.keySet()){
            // 如果有多个数组元素,它们的度都是maxCount,则选择长度最小的数组元素,
            // 以这个数组元素为key,获取其第一次出现与最后一次出现之间的长度,其长度保存在map3当中
            if(maxCount == map1.get(key)){
                minLength = Math.min(minLength, map3.get(key));
            }
        }
        return minLength;
    }
}

收获:

1、学会使用javaAPI来解决问题,比如map这类结构
2、思路清晰,先列草稿来大概理清思路,然后再动笔实现
3、大胆猜测,然后再去证明

第三题:448. 找到所有数组中消失的数字
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

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

思路:

思路1:使用外部结构hashmap
循环数组,然后将数组值nums[i]做为map的key,然后至于map的value其实无所谓,就用Boolean表示即可
然后从1遍历到数组长度n,去看map中的key是否包含了i,没包含就说明缺少了,返回放入list中。

官方讲解:
·我们用一个哈希表 hash 来记录我们在数组中遇到的数字。我们也可以用集合 set 来记录,因为我们并不关心数字出现的次数。
·然后遍历给定数组的元素,插入到哈希表中,即使哈希表中已经存在某元素,再次插入了也会覆盖
·现在我们知道了数组中存在那些数字,只需从 1⋯N 范围中找到缺失的数字。
·从 1⋯N 检查哈希表中是否存在,若不存在则添加到存放答案的数组中。
public List<Integer> findDisappearedNumbers(int[] nums) {
        //第一步:循环数组,然后将数组值nums[i]做为map的key
        Map<Integer,Boolean> map = new HashMap<>();
        for(Integer num : nums){
            if(!map.containsKey(num)){ //只有当map中没得才添加,有了就不要添加了
                map.put(num,true);
            }
        }

        //从1遍历到数组长度n,去看map中的key是否包含了i
        List<Integer> result = new LinkedList<>();
        for (int i = 1; i <= nums.length; i++) {
            if(!map.containsKey(i)){
               result.add(i) ;
            }
        }
        return result;
    }
思路2:抽屉法,原地置换法
1、遍历数组,得到每一个元素对应的那个下标(|nums[i]|-1)(就是nums[0]的绝对值-1作为新的下标)
2、当那个下标的值大于0,那我就将它置为相反数(负数)
3、然后遍历数组,当数组中存在大于0的,那就说明nums[i]的i+1的值就是答案!

官方讲解:
·遍历输入数组的每个元素一次。
·我们将把 |nums[i]|-1 索引位置的元素标记为负数。即nums[∣nums[i]∣−1]×−1 。
·然后遍历数组,若当前数组元素 nums[i] 为负数,说明我们在数组中存在数字 i+1。
public List<Integer> findDisappearedNumbers2(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            int index = Math.abs(nums[i])-1;  //遍历数组,得到每一个元素对应的那个下标
            if(nums[index]>0){
                nums[index] = nums[index] * (-1); //当那个下标的值大于0,那我就将它置为相反数(负数)
            }

        }

        List<Integer> result = new LinkedList();
        for (int i = 0; i < nums.length; i++) {
            if(nums[i] >0){
                result.add(i+1); //然后遍历数组,当数组中存在大于0的,那就说明nums[i]的i+1的值就是答案!
            }
        }
        return result;
    }

第四题:442. 数组中重复的数据
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?

示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[2,3]

思路:

这也能算是中等题目?就这?(我瞎逼逼的哈,我膨胀了)
一看到这个题目就要想到使用hashmap或者原地置换方法
1、遍历数组
2、找到每一个元素对应的置换下标:int newIndex = Math.abs(nums[i]) -1;
3、如果位置newIndex 上的数字已经为负,则newIndex+1是出现两次的数字

原地置换做为数组类题目中常见的方法,一定要经常复习哦!

代码:

public List<Integer> findDuplicates(int[] nums) {
        List<Integer> list = new LinkedList<>();
        for (int i = 0; i < nums.length; i++) {
            int newIndex = Math.abs(nums[i]) -1; //找到每一个元素对应的置换下标
            if(nums[newIndex] <0){ //如果位置newIndex 上的数字已经为负,则newIndex+1是出现两次的数字
                list.add(newIndex+1);
            }else {
                nums[newIndex] *= -1; //否则就置为负数
            }

        }
        return list;
    }

第五题:41. 缺失的第一个正数
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

示例 1:
输入: [1,2,0]
输出: 3
示例 2:
输入: [3,4,-1,1]
输出: 2
示例 3:
输入: [7,8,9,11,12]
输出: 1
提示:
你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。

思路:

本人愚见:
1、先排好序
2、然后控制数组从第一个正整数开始(用Linkedlist来实现)
3、然后对Linkedlist去重处理
4、从1开始遍历到list的长度;然后list里面的元素也开始遍历,当不相等的时候就是缺的那个

本人在去重的那个地方花了很多时间,而且这个思路还不符合规矩,时间和空间复杂度不符合,但是也让我通过了
public int firstMissingPositive(int[] nums) {

        if(nums.length==0){
            return 1;
        }else{
            Arrays.sort(nums); //排序

            int result = 0;
            //构建list
            List<Integer> list1 = new LinkedList<>(); 
            for (int i = 0; i < nums.length; i++) {
                if(nums[i]>0){
                    list1.add(nums[i]);
                }
            }
            //list去重
            LinkedList<Integer> list = new LinkedList<>();

            Iterator<Integer> it=list1.iterator();
            while (it.hasNext()){
                Integer next = it.next();
                if (!list.contains(next)){
                    list.add(next);
                }
            }

            if(list.size()==0){
                return 1;
            }else {
                for (int i = 1; i <=list.size(); i++) {
                    if(list.get(i-1) != i){ //从1开始遍历到list的长度;然后list里面的元素也开始遍历,当不相等的时候就是缺的那个
                        result = i;
                        break;
                    }else {
                        result = i+1;
                    }
                }
                return result;
            }
        }
    }

官方题解:https://leetcode-cn.com/problems/first-missing-positive/solution/que-shi-de-di-yi-ge-zheng-shu-by-leetcode-solution/