剑指offer03-数组中重复的数字(java)|leetcode刷题

题目

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。在解答该题的时候大家可以根据自己对时间和空间的要求去选择合适的算法。
示例 1:

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

限制:

2 <= n <= 100000

题目分析

数组的长度为n且数组的元素内容为0到n-1,我们知道数组的下标就是从0开始到n-1,这个条件就让我们对这个题目有了新的优化思路,具体见下文题解。
数组中重复的元素可能有很多,这里不要求找出全部的重复元素,只要找出的元素是其中之一即可,一般情况下找出的都是第一个出现的重复元素。

题解

方法一 哈希表

利用哈希表中元素不能重复的特点,我们先创建一个hashset然后将数组的元素依次添加进去,如果该元素在hashset中不存在,则添加成功,否则添加失败说明数组中该元素已经存在,返回添加失败的元素即为重复的第一个元素。

/**
     * 哈希表
     * 时间复杂度是 O(n)
     * 空间复杂度是 O(n)
     * @param nums
     * @return
     */
    public static int findRepeatNumber(int[] nums) {
    
    
        Set<Integer> set = new HashSet<>();//hash表
        int res = -1;
        for (int num:nums){
    
    
            if (!set.add(num)){
    
     //不允许重复元素添加,添加失败返回0
                res = num;
                break;
            }
        }
        return res;
    }

由于for循环遍历数组,时间复杂度为O(n);
由于创建hash表需要申请额外的n个空间,空间复杂度为O(n);

方法二 暴力遍历数组

直接使用数组的遍历,遍历数组中的每一个元素,然后在剩下的元素中寻找是否存在相同的元素,如果存在直接返回。如果遍历完数组还没有找到重复的元素则表示数组中不存在重复元素,返回-1。

**
     * 暴力求解
     * 时间复杂度是 O(n^2)
     * 空间复杂度是 O(1)
     * @param nums
     * @return
     */
    public int findRepeatNumber(int[] nums) {
    
    
        for (int i = 0; i < nums.length; i++) {
    
    
            for (int j = i + 1; j < nums.length; j++) {
    
    
                if (nums[i] == nums[j]) {
    
    
                    return nums[i];
                }
            }
        }
        return -1;
    }

方法三 排序法

因为数组中的元素是在0~数组长度-1,所以可以将数组的元素进行排序,排序后的数组中重复元素是相邻的,通过判断一个元素的相邻元素是否和它相同在查找重复元素。

public int findRepeatNumber5(int[] nums) {
    
    
        // 排序后的数组,重复元素必然相邻
        Arrays.sort(nums);
        int res = 0;
        for(int i = 0; i < nums.length - 1; i++){
    
    
            // 找到重复元素
            if(nums[i] == nums[i+1]){
    
    
                res = nums[i];
                break;
            }
        }
        return res;
    }

这里Arrays.sort()方法的时间复杂度具有不确定性,当数组中元素个数小于286且小于47进行插入排序,元素个数小于286但是大于47的进行快速排序;元素个数大于286且数组不具备结构的按照小于286的进行看待,元素个数大于286且数组具备结构的进行归并排序,每种排序的时间复杂度都不一样。
对于Arrays.sort()方法的底层原理大家不了解的话可以看
这篇文章

方法四 利用数组下标和元素的特点新建数组

数组长度为n,利用数组下标和数组元素都是从0~n-1,我们可以将数组的元素当作下标使用,新建一个全是-1的数组,然后遍历原来的数组,将数组的元素作为下标,数组的下标作为值存入新数组中,一个下标被访问一次的元素只出现一次,一个下标被多次访问的元素就是重复出现的元素。(新数组初始化为全是-1是因为原数组的元素都是0 ~n-1,便于后续访问的时候判断是否访问过 )

public int findRepeatNumber(int[] nums) {
    
    
    int[] res = new int[nums.length];
    Arrays.fill(res, -1);
    for (int i = 0; i < nums.length; i++) {
    
    
        // 2. 判断当前元素是否已经存在
        if (res[nums[i]] != -1) {
    
    
            // 如果存在,则直接返回
            return nums[i];
        }// 否则的话,将当前元素作为索引,当前元素的下标作为值,填入新建的数组中,如果新建数组中该位置是-1,说明当前元素是第一次出现;如果新建数组中该位置已经被其他数字填充了,说明当前元素重复出现
        res[nums[i]] = i;
    }
    return -1;
}

时间复杂度是 O(n)
空间复杂度是 O(n)
在这里使用数组比使用哈希表能更节省空间,且使用数组的性能更高。哈希表底层使用的是数组加链表还有红黑树,它的数组一般都是用不满的就存在浪费空间。而且哈希表中查找的时候要进行哈希计算,有可能出现冲突。

方法五 数组原地交换

利用方法四的思想,但是方法四中新建的数组增加了空间复杂度,其实我们不新建数组也可以使用该思想完成。

public static int findRepeatNumber4(int[] nums) {
    
    
        for (int i = 0 ;i < nums.length;i++){
    
    
            while (i!=nums[i]){
    
    
                if (nums[nums[i]] == nums[i]){
    
    
                    return nums[i];
                }
//                int temp = nums[i];
//                nums[i] = nums[nums[i]];
//                nums[nums[i]] = temp;
                // 交换
                int tmp = nums[nums[i]];
                nums[nums[i]] = nums[i];
                nums[i] = tmp;
            }
        }
        return -1;
    }

时间复杂度是 O(n)
空间复杂度是 O(1)
注意:使用该方法的时候需要注意,交换nums[i]和nums[nums[i]]的时候不能先进行nums[i] = nums[nums[i]],这样的话nums[i]改变了nums[nums[i]]也随之改变。
以下是我画图的一个分析(有点丑大家表介意):

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43941925/article/details/112756052
今日推荐