寻找数组中重复的数字(几个尽可能节省空间复杂度的做法)

题目一: 在一个长度为n的数组里的所有数字都在0 ~
n-1的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次.请找出数组中任意一个重复的数字.例如,如果输入长度为7的数组{2,
3, 1, 0, 2, 5, 3},那么对应的数字是重复的数字2或3.

算法思想:

对数组排序,令数组元素(数字)和其下标值相等.若无重复元素,则元素和下标一一对应.若有重复元素,则一定存在某个下标对应多个元素.
但是根据规则,一个下标只能存放一个元素,这就必然存在某个下标,它对应的元素和下标的值不相等.
如何找到这个下标:
只要发现某个下标i,和其对应的元素值不相等.就找下标j = arr[i].如果出现这种情况:arr[j] = arr[i] = j.或者说下标j和它对应的位置元素值相等,这就说明了下标j才是元素arr[i]真正应该存放的位置,只不过这个位置被抢了,这就说明了arr[j] = arr[i].也就找到了这个重复的元素.
这个方法时间复杂度是O(n),空间复杂度是O(1).

python代码实现

#一个长度为n的数组,数组里的元素范围是0-n-1.请判断这个数组是否存在重复元素并找出这个重复元素,
def getDuplicateNum(arr, length):
    #排除特殊情况
    if length == 0:
        return -1
    for i in range(length):
        if arr[i] < 0 or arr[i] > length - 1:
            return -1

    for i in range(length):
        print(arr)
        while(arr[i] != i):
            j = arr[i]
            if arr[j] == j:
                print("这个数组里有重复的数字,它是", arr[j])
                return True
            #否则交换下标i和下标arr[i]对应位置的元素
            arr[i], arr[j] = arr[j], arr[i]
    print("这个数组里没有重复的数字.")
    return False

if __name__== "__main__":
    arr = [3, 2, 1, 0, 2, 5, 4]
    length = len(arr)
    getDuplicateNum(arr, length)
    arr1 = [5, 3, 2, 1, 5, 4]
    length = len(arr1)
    getDuplicateNum(arr1, length)

在这里插入图片描述

题目二:不修改数组找出重复的数字
在一个长度为n+1的数组里的所有数字都在1 ~ n的范围内,所以数组中至少有一个数字是重复的.请找出数组中任意一个重复的数字,但不能修改输入的数组.例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或3.

算法思想:

我们把1 ~ n 范围内的数字按照中间的数字m分为两部分,前面一半是1 ~ m,后面一半是m+1 ~ n.
如果1 ~ m 的数字的数目超过 m, 那么这一半的区间里一定包含重复的数字;否则, 另一半 m + 1 ~ n的区间里一定包含重复的数字.我们可以继续把包含重复的数字的区间一分为二,直到找到一个重复的数字.
注意:这种方法不一定能找到所有重复的数字,但一定能找到重复数字. 举个栗子.如果是这样一个数组[1, 1, 1, 2, 3, 4, 4, 6],数组的范围是1 ~ 5, 数组的长度是8.这一定是有重复元素的.跟据中间数3二分,1-3之间的数字有5个,超过了1-3这个范围,所以一定有重复数字.但4-6之间的数字有三个,并没有超过这个范围,但还是有重复的数字.也就是说二分的方法并不会找到重复的数字4.
或者说如果有一个数组的范围是1到5.但数组中只有2个元素,是不能说明数组中没有重复的元素的,因为它可以是[3,4],也可以是[3,3].总之都符合范围是1到5这个条件,但却不能确定是否有重复元素.
这里也是一样,我们在二分的时候,是对数的范围二分而不是对数组的长度二分.我们可以确定在这个范围内的数数目超过这个范围的,一定有重复元素,但不能确定在这个范围内数的数目不超过这个范围的一定没有重复元素.但有没有可能在进行二分以后,两边的范围里的数数目都没有超过各自的范围的情况呢?这不可能. 假设这可能,那么这两边范围内各自的数数目之和没有超过这两个范围之和,这两个范围之和就是原范围.如果说在一开始二分时就没有找到这样的范围,使得范围里的数的个数超过了这个范围的长度,那就是说范围1 - n的数的个数没有超过1到n.这是不符合题目要求的. 换言之,如果题目没有这样的要求,用二分的方法很可能是求不出来的.比如,叫你找到数组[1, 2, 2, 8, 5, 6, 7, 7, 11]中是否有重复的数字就是这么一个情况,这个范围已经是1 ~ 11了,但数字的个数还没有超过这个范围.这就不能用二分法去找是否有重复的数字在里面.沿着这个思路想,就能得出在任何一个二分时刻都不会出现没有一个范围里的数的个数超过这个范围的长度的情况.

python代码实现

#给一个数组,长度为n+1, 它保存到数字是范围1 ~ n的.所以这个数组一定至少有一个重复的数字
#采用二分法,记最小数为start, 最大数为end.每次取数的中间范围middle = (start+end)/2;
#如果在start ~ middle或middle+1 ~ end的任一范围内,有这个范围里的数数目超过这个范围长度
#的情况,那么就可以缩小范围了,要么是start = middle + 1, 要么是 end = middle.直到start == end
#为止.此时middle = start = end.数middle就是这个重复的数.

def countRange(arr, length, start, end):
    if arr == None:
        return -1
    count = 0
    for i in range(length):
        if(arr[i] >= start and arr[i] <= end):
            count += 1
    return count


def getDuplication(arr, length):
    if length <= 0:
        return -1

    start = 1
    end = length - 1
    while(end >= start):
        middle = ((end - start) >> 1) + start #这相当于求(end + start) / 2.只不过逼格更高.
        count = countRange(arr, length, start, middle)
        if end == start:
            if count > 1:
                print("找到了这个重复的元素:", start)
                return start
            else:
                break
        if count > middle - start + 1:
            end = middle
        else:
            start = middle + 1

参考结果:

if __name__ == "__main__":
    arr = [7, 3, 2, 4, 5, 7, 6, 5]
    print("这个数组为:" , arr)
    length = len(arr)
    getDuplication(arr, length)

    arr1 = [2, 3, 5, 7, 1, 6, 4, 5]
    print("这个数组为:" , arr1)
    length = len(arr1)
    getDuplication(arr1, length)

    arr2 = [2, 3, 5, 4, 3, 2, 6, 7]
    print("这个数组为:" , arr2)
    length = len(arr2)
    getDuplication(arr2, length)

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值