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

03. 数组中重复的数字
一、题目描述
  • 找出数组中重复的数字。
  • 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字
二、示例描述
  • 输入:
    [2, 3, 1, 0, 2, 5, 3]
    输出:2 或 3
    
    限制:2 <= n <= 100000
    
三、解法分析

方案一:暴力求解

  • 遍历数组,将数组中的每个元素与其他元素进行对比,有相同的就进行输出即可

代码实现:

public class Solution {
    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;
    }
}

算法复杂度分析:

  • 时间复杂度O(n^2)
  • 空间复杂度O(1)

存在的问题:限制:2 <= n <= 100000,时间复杂度太高,提交代码结果会“超过时间限制”

  • 剑指Offer书中提到一个方法,先将数组进行排序后,在从排序的数组中长出重复的数字。排序后遍历数组两两相邻元素比较即可找出重复元素。排序时间复杂度为O(nlogn),遍历时间复杂度O(n)

方案二:哈希表

  • 题目如果改成是统计数组中每个元素出现的次数,最先想到的是Map。遍历数组以元素作为key,元素出现次数作为value。主要使用的就是Map中的key唯一的这一点特性。同样能保证数据唯一的还有Set。
  • 使用哈希表来解决该问题:
    • 遍历数组,每扫描到一个数字时,都可以使用O(1)的时间来判断哈希表里是否包含该数字
    • 如果哈希表中没有该数字,就将该数字加入到哈希表
    • 若果哈希表中已有该数字,则找到了重复数字,返回该数字即可
  • 这里用到了典型的空间换时间的思想,此种方式的时间复杂度减少为O(n),但提高时间效率是以大小为O(n)的哈希表为代价的。

代码实现:

public class Solution {
    public int findRepeatNumber(int[] nums) {
    
        HashSet hashSet=new HashSet();
        for (int num : nums) {
            if(hashSet.contains(num)){
                return num;
            }
            hashSet.add(num);
        }
        return -1;
    }
}

方案三:原地置换

  • 注意数组中的数字范围是0~n-1,如果数组没有重复数字,排序后索引 i 处的元素也应是 i 。由于数组中有重复数字,则有的位置存在多个重复数字。
  • 一个萝卜一个坑:
  • 【萝卜坑】萝卜 ——>n 【索引】元素
  • 遍历萝卜坑,每次遍历都是为了给萝卜坑找到对应编号的萝卜
    {【0】2,【1】3,【2】1,【3】0,【4】2,【5】5,【6】3}
  • 首先遍历到萝卜坑【0】,发现坑里是萝卜2,那你就看看萝卜坑【2】中的萝卜是不是也放错了,发现人家不匹配,就舍己为人,先让人家配对,现将萝卜坑【0】和萝卜坑【2】中萝卜交换。(因为你的目的是萝卜坑和萝卜匹配,你不匹配那就先让人家匹配吧,而且人家坑里的萝卜不能扔了吧,你得给带走,那就进行交换。交换完了你可能还是不匹配,但是你已经让其中的一个萝卜坑匹配了萝卜)
    {【0】1,【1】3,【2】2,【3】0,【4】2,【5】5,【6】3}
  • 这时候萝卜坑【0】中是萝卜1,还是不匹配,发现萝卜坑【1】也不匹配,现在讲萝卜坑【0】和萝卜坑【1】中萝卜交换
    {【0】3,【1】1【2】2,【3】0,【4】2,【5】5,【6】3}
  • 这时候萝卜坑【0】中是萝卜3,还是不匹配,发现萝卜坑【3】也不匹配,现在讲萝卜坑【0】和萝卜坑【3】中萝卜交换
    {【0】0,【1】1【2】2【3】3,【4】2,【5】5,【6】3}
  • 这时候萝卜坑【0】中是萝卜0,匹配,接着进行遍历
  • 遍历时发现萝卜坑【1】【2】【3】都匹配,那就啥都不用做
  • 遍历到了萝卜坑【4】,发现是萝卜2,不匹配,然后想跟萝卜坑【2】中萝卜进行交换,发现人家萝卜坑已经匹配到萝卜,那这个萝卜2就是多余了,重复了
  • 输出萝卜2即可
遍历萝卜坑{
	当前萝卜和萝卜坑不匹配 (循环){
		如果 萝卜应当待的坑中 坑和萝卜匹配{
			当前萝卜是多余重复的
		}
		将当前坑中萝卜 和 萝卜叮当待的坑中萝卜 交换
	}
}

代码实现:

public class Solution {
    public int findRepeatNumber(int[] nums) {
        
        int length = nums.length;
        if(nums == null || length<=0){//空数组
            return -1;
        }

        for(int i = 0; i < length; i++){
            if(nums[i]<0 || nums[i]>length-1){//数组不满足条件
                return -1;
            }
        }
        
        for (int i = 0; i < length; i++){
            while (nums[i] != i) {
                if (nums[nums[i]] == nums[i]) {
                    return nums[i];
                }
                int temp = nums[i];
                nums[i] = nums[temp];
                nums[temp] = temp;
            }
        }
        return -1;
    }
}

算法复杂度分析:

  • 时间复杂度:代码中有两个循环for/while,所以大家都会认为算法复杂度是O(n^2),但是每个数字最多只要交换两次就能够找到属于自己的位置,因此时间复杂度是O(n)
  • 空间复杂度:所有的操作都是在原本的数组中进行的,不需要额外分配内存,因此空间复杂度是O(1)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值