《剑指offer》-不修改数组,找出重复的数字

本文介绍两种在不修改原数组的情况下寻找重复数字的方法:一种使用辅助数组实现O(n)时间复杂度,另一种采用二分查找策略,实现O(n*logn)时间复杂度,探讨其优劣及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不修改数组,找出重复的数字

在一个长度为 n+1 的数组里的所有数字都在 1~ n 的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。数组中某些数字是重复的,但是不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如:如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出应该是重复的数字2或者3
第一种方法:类似于桶排序,

创建一个额外的辅助数组,将原数组中的数字 m,复制到辅助数组中下标为 m 的位置,如果已经赋值过了,则肯定是重复的
时间复杂度:遍历了一次原数组,所以为O(n)
空间复杂度:创建了额外的辅助数组,O(n)

第二种方法:二分查找。

以范围内的中点记为 m, 如果 1-m 之间的数字,在原数组中出现了超过m次,则这个范围内肯定有重复数字;如果在这个范围内没有重复的数字,这个范围内的数字出现的次数肯定是小于等于 m 的。
时间复杂度:Count函数被调用了 logn 次,每次都遍历数组,所以为O(n*logn)
空间复杂度:O(1)

int[] numbers = {2, 3, 5, 4, 3, 2, 6, 7};
int len = numbers.length; 
int start = 1; // 用于存储查找数字范围的开头
int end = len; // 用于存储查找数字范围的结尾
int middle = (end - start)/2 + start; // 用于存储,查找数字范围的中间数字
int count = 0; // 确定范围内数字出现的次数
while (start <= end){ // 这个几乎是永久成立的,只有break/return能退出循环
    middle = (end - start)/2 + start; // 更新middle
    count = Count(numbers, len, start, middle); // 统计
    if (end == start){   // 如果 end=start,middle也等于start,就相当于统计的是start这个数字出现的次数
        if (count > 1){
            System.out.println(start);
            break;
        }else{
            break;
        }
    }
    if (count > (middle - start + 1)){ // 如果数字出现的次数大于范围,说明这个范围内有重复的数字
        end = middle; // 缩小范围,继续统计
    }else{
        start = middle + 1; // 如果数字出现的次数小于范围,则说明范围内没有重复的数字,则缩小范围。
    }
}
/*  统计在原数组中的所有数据,在 start~middle 这个范围内的数字出现的次数  */
public static int Count(int[] numbers, int len, int start, int middle){
    int count = 0;
    for(int i=0;i<len;i++){
        if(numbers[i] >= start && numbers[i] <= middle){
            count++;
        }
    }
    return count;
}

测试用例:

  1. 长度为n的数组里包含1个或多个重复的数字
  2. 数组中不包含重复的数字。如果没有重复的数据,应该在循环结束之后返回
  3. 无效的输入测试用例
    1. 空数组:这里需要在主函数开头、统计函数开头进行判断,如果是空数组或者只有一个数字的数组,则直接返回没有
    2. 长度为n的数组中包含 大于 n-1 的数字:可以先遍历一次

问题:
我觉得这个算法有一个致命的缺陷,就是说,如果在范围内,比如1 ~ 4内, 只有3这个数字出现了3次,那么按照这个算法,虽然3也是重复的数字,但是它却找不到。也就是说,这个统计函数返回的数字无法确定是某个数字多次出现的结果,还是每个数字都出现了一次的结果。但是此时在 5-7这个范围内,必然还存在着其他重复的数字,这个算法能找到5-7范围内的重复的数字。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值