leetcode 剑指 Offer 03. 数组中重复的数字

剑指 Offer 03. 数组中重复的数字

找出数组中重复的数字。

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

示例 1:

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

限制:

2 <= n <= 100000

解法一:计数排序

思路:

利用计数排序,在统计次数的时候判断出现次数是否大于1,如果大于1直接返回该数

 1 class Solution {
 2     public int findRepeatNumber(int[] nums) {
 3         int len = nums.length;
 4         // 计数排序,如果数量大于1则返回
 5         int[] counts = new int[len];
 6         for(int i = 0; i < len; i++){
 7             counts[nums[i]]++;
 8             if(counts[nums[i]] > 1){
 9                 return nums[i];
10             }
11         }
12         return -1;
13     }
14 }

leetcode 运行时间为2ms, 空间为45.9MB

复杂度分析:

时间复杂度:计数排序,但是不需要统计完,所以平均时间复杂度为O(n/2)

空间复杂度:需要创建一个和nums数组等大的数组,所以空间复杂度为O(n)

解法二:

思路来源:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/solution/yuan-di-zhi-huan-shi-jian-kong-jian-100-by-derrick/

在元素不重复的情况下,但数组有序时,每个元素所在的位置和这个元素的值相等。所以我们遍历数组,把当前元素放到正确的位置,也就是以这个元素为下标的位置,把当前元素和他正确的位置的元素进行交换。重新安排位置的过程中,肯定就会有元素重复的情况。返回即可

class Solution {
    public int findRepeatNumber(int[] nums) {
        int len = nums.length;

        for(int i = 0; i < len; i++){
            // 如果当前元素不在正确的位置
            if(nums[i] != i){
                // 如果重复,直接返回
                if(nums[i] == nums[nums[i]]){
                    return nums[i];
                }
                // 把当前元素放到正确的位置
                int temp = nums[nums[i]];
                nums[nums[i]] = nums[i];
                nums[i] = temp;
            }
        }
        return nums[len - 1];
    }
}

leetcode运行时间为0ms, 空间为48.8MB

复杂度分析:

时间复杂度:最多完整的遍历一次数组,平均复杂度为O(n/2)

空间复杂度:O(1)

golang 版本

func findRepeatNumber(nums []int) int {

    // 在元素不重复且有序时,每个元素都跟它的下标相等,如果数组有序但存在重复元素,那么必定导致多个值因为抢占同一个下标位置而冲突,所以如果我们遍历元素的过程中,把当前元素放到它正确的位置,
    // 因为遍历数组的过程中,每次必定会把一个元素放到正确的位置,遍历完成后,刚好把 n 个元素都放到了正确的位置,由于恰好 n 等于数组长度,如果数组不存在重复,那么此时应该是有序的,如果数组存在重复,那么在遍历过程中必行存在正确位置已经有正确值的情况发生。
    // 那么在数组优化的过程中,肯定会出现某元素对应的正确位置已经有正确值的情况,这时就是找到了重复元素
    for i, val := range nums {
        // 如果当前元素恰好在正确位置,那么跳过,如果当前元素不在正确位置,那么判断它正确位置是否存在正确的值
        if i != val {
            if val == nums[val] {
                return val
            } 
            nums[i], nums[val] = nums[val], val // 如果正确位置存在正确的值,说明重复,否则交换当前元素和此时正确位置的值,把当前元素放到正确位置
            
        }
        
    }
    return nums[len(nums) - 1]
}

解法三:利用HashSet

判断当前元素是否存在于HashSet,如果存在说明是重复元素,直接返回,否则把这个元素存入HashSet

 1 class Solution {
 2     public int findRepeatNumber(int[] nums) {
 3         int len = nums.length;
 4 
 5         HashSet<Integer> hs = new HashSet<Integer>();
 6         for(int num : nums){
 7             if(hs.contains(num)){
 8                 return num;
 9             }
10             hs.add(num);
11         }
12         return -1;
13     }
14 }

leetcode运行时间为10ms, 空间为47.6MB, 可以看到这个运行时间远大于上面两种方法,所以知道对HastSet虽然也是以hash函数来存取元素的,但是效率远低于直接操作数组。

复杂度分析:

时间复杂度:最多完整的遍历一次数组,平均复杂度为O(n/2)

空间复杂度:hashset中存储的元素个数,O(n)

下面这个解法有问题

本来是打算把遇到的每个数,对应的下标的元素置为负数,如果某个数重复,那么对应下标的元素第二次碰到时就会发现是负数,从而把下标返回,但是发现有边界 case,如果重复的元素对应的下标的值为0,那么第一次不会被置为负数,第二次也因为识别到 0 不是负数而跳过,从而错过了正确结果。

边界 case 举例:[1, 0, 1, 4, 2, 5, 3],输出 -1,实际结果为 1。

func findRepeatNumber(nums []int) int {

    // 遍历数组,把当前值对应的下标对应的元素标记为负数,如果当前值为负数,则对当前值取绝对值
    for _, val := range nums {
        if val < 0 {
            val *= -1
        }
        if nums[val] < 0 { // 判断当前值对应的下标对应的元素是否为负数,如果为负数,直接返回当前值,说明之前碰到过一次
            return val
        } else {
            nums[val] *= -1 // 否则把对应下标的元素置为负数
        }
       
    }    
    return nums[0]
}

 

posted @ 2020-09-29 18:50  Lucky小黄人^_^  阅读(197)  评论(0编辑  收藏  举报