zoukankan      html  css  js  c++  java
  • 一万个数查找两个重复数,快速二分查找法 O(logN)(转)

    题目:1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?

    一、有序情况

    有1001个数,按照从小到大的顺序排列,其中只有两个数是重复的。当然,因为有序,装两个数也必定是挨着的。
    题目的要求就是用最少的内存开销、最少的时间开销找出这个重复的数字

    莫贝特 的 博客中,他的算法是将(1001个数字的和)- (1000个数字的和)=  重复数字,这种算法思路简单,但是时间复杂度是最高的,共进行了2000次循环,至于留言中的其他算法也基本上属于线性查找,线性查找的时间复杂度是 O(N),对于这道题目,运气最好的时候第一次就能找到,运气最差的时候,要找1000次,平均要找500次。

    static void Main(string[] args)
            {
               int[] list = new int[1001];
               for (int i = 1; i < 1001; i++)
              {
                    list[i - 1] = i;
               }
    
                Random random = new Random();
                list[1000] = random.Next(1, 1000);
                int sum1 = 0;
                int sum2 = 0;
                foreach (int i in list)
               {
                   sum1 = sum1 + i;
                }
               for (int i = 1; i < 1001; i++)
                {
                   sum2 = sum2 + i;
                }
    
              Console.WriteLine("重复的数字是:"+(sum1 - sum2).ToString());
                Console.Read();
            }

    然而,如果我们考虑到这是一个有序数列,而且只有两个重复数,二分折半查找法就一跃而出了,对数据结构和算法有了解的人都知道,二分查找法是有序数列最好的查找算法。
    具体思路是:

    1. left=0
    2. right=10001
    3. 如果 left 和 right 中间那个位置 pos 的数值等于位置编号 pos,则:
    4. left=pos,否则:
    5. right=pos
    6. 如果(right-left>1),则重复步骤 3)-6),直到 right-left=1, pos 就是要查找的数

    1000个数只需查找10次,一万个数13次,十万个数17次,一百万个数20次,时间复杂度 O(logN),速度很快。

    #!/usr/bin/python
    # -*- coding: GBK -*-
    import random
    
    # 生成数列,一万个数
    arr_length=100001
    list=range(1,arr_length)
    
    #插入一个随机数
    rnd=random.randrange(1,arr_length)
    list.insert(rnd-1,rnd)
    
    print '\n重复数为:'+ str(rnd)
    print'\n开始查找'
     
    # 正式开始查找算法   
    left=0
    right=arr_length
    count=0
    
    while ((right-left)>1):
        pos=(left+right)/2    
        count=count+1
        print '第 %d 次定位:%d' % (count,pos)    
        if (list[pos]==pos):
            right=pos
        else:
            left=pos    
    
    print '\n查找结果:',list[left]

    注:先附上原作者的Python,找个时间再转为java

    二、无序情况

    上面的算法是:(1001个数字的和)- (1000个数字的和)=  (1001个数字的和)- n*(n+1)/2=  重复数字,求和需要1000次循环。
    这个算法之所以能成立,是因为序列由1-1000的连续数字组成,因此,不管他们怎么排列,(1000个数字的和)都是从1加到1000。但是,如果这1000个数字不是连续的,比如是从1-100000中随机选出来的互不相同的1000个数,那么这个算法就不成立了,因为我们无法知道(1000个数字的和)到底是哪1000个数字。

    既然现在给出的条件是1-1000的乱序,那么就可以利用连续这一特殊性来改进算法。

    最简单的方法应该是:再开辟一个1000空间的数组 list2 ,将这1001个数字从第一个开始,依次放到 list2 中他的编号位置上
    比如:
    list2[list[0]-1]=list[0]
    list2[list[1]-1]=list[1]
    依次这样做下去,直到某个要保存的数字在 list2 对应的位置上已经存在,就找到了这个数字。
    但是这次我看清要求了,不能开辟新的空间。

    虽然不能开辟新的空间,思路却已经有了,就是利用连续性和索引的对应关系,具体算法描述如下:
    1、取出第一个位置的数字:p1=list[0]
    2、将第一个位置标记:list[0]=0
    3、取出 p1 位置的数字:p2=list[p1]
    4、将 p1 位置标记:list[p1]=0
    5、取出 p2 位置的数字:p3=list[p2]
    6、将 p2 位置标记:list[p2]=0
    .............
    依次做下去,直到下一个要找的位置被标记过了,数字就找到了。
    原因是:那个重复的数字,最终要访问他自己的位置第二次,当他访问时,发现位置已经被上一个自己访问过了,他就知道自己不是唯一的了。

    这种算法叫什么名字我也不知道,能不能叫漫游算法?
    在最好的情况下,他的循环次数是 1,比如[1,1,....1000]
    在最坏的情况下,他的循环次数是 1000,比如[1,2,3......1000,1000]
    平均情况下,他的循环次数是 N/2,虽说数量级上仍然是 O(N)级别的,但是平均来说,仍然比前面的求和法快了一倍

    #!/usr/bin/python
    # -*- coding: GBK -*-
    import random
    
    # 生成数列
    arr_length=10001
    list=range(1,arr_length)
    
    #加入一个随机数
    rnd=random.randrange(1,arr_length)
    list.append(rnd)
    print '\n重复数为:'+ str(rnd)
    
    #打乱顺序
    random.shuffle(list)
    
    # 正式开始查找算法 
    count=0
    idx=0  
    while (list[idx]!=0):
        count=count +1
        temp=list[idx] 
        list[idx]=0
        idx=temp    
    
    print "找到:%d,循环 %d 次" % (temp,count)

    注:先附上原作者的Python,找个时间再转为java

  • 相关阅读:
    .NET开发中应该遵循的几点建议
    .NET开发中应该遵循的几点建议
    .NET开发中应该遵循的几点建议
    查询CPU占用高的SQL语句的解决方案
    查询CPU占用高的SQL语句的解决方案
    HTTP消息中Header头部信息整理
    HTTP消息中Header头部信息整理
    HTTP消息中Header头部信息整理
    mac搭建本地服务器
    常用的GIT
  • 原文地址:https://www.cnblogs.com/JoannaQ/p/2968458.html
Copyright © 2011-2022 走看看