一万个数查找两个重复数,快速二分查找法 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

转载于:https://www.cnblogs.com/JoannaQ/archive/2013/03/19/2968458.html

猜你喜欢

转载自blog.csdn.net/weixin_34308389/article/details/93055977