零零散散学算法之找出数组中重复的数---总结篇

来源:互联网 发布:java流程设计 编辑:程序博客网 时间:2024/04/30 07:36
找出数组中重复的数

前序
        最近一直在看v_JULY_v的专栏,从中学到了很多关于算法方面的知识,也受到了很大的启发。我相信喜欢算法的朋友,看过他的博文之后也会有这种想法。前段时间参加了一些面试,从他的专栏里学到的算法给予了我不小的帮助,这让我在面试的时候轻松了不少。他的博文我将会继续关注和学习。

        好了,我们言归正传。关于找出数组中重复数的这个问题,网上已经有很多文章来阐述。不过我认为各位对于这个问题考虑的情况不是很全面,于是自己就想把这个问题完善一下。

        问题描述:给你一个数组A[N],该数组中存放着N个自然数,要求是找出数组中重复的那个数。

        对于这个问题,我认为可以分两种情况讨论。
        情况一:这N个数的大小在1---N-1之间。如A[5]:2、1、3、1、4。
        情况二:这N个数的大小是任意的,这N个数可能在1---N-1这个区间里,也可能不在。如A[7]:1、3、5、7、9、9、11。
        其实情况一就是情况二的一个特例罢了。不过适合情况一的解法却不一定适合情况二,但是适合情况二的则一定适合情况一。

        对于第一种情况,也就是这N个数的大小在1---N-1之间,有很多种方法可以实现。在这,我只讲只适合此种情况却不适合情况二的解法。
        解法一:我最先想到的就是利用数学的方法,我叫它求和做差法。这种方法的思想是:声明两个变量TempSum1、TempSum2,利用一个简单的for循环,即TempSum1=1+2+...N,TempSum2=A[0]+A[1]+...A[N-1],于是通过N-(TempSum1-TempSum2)便可得到这个重复的数。实现代码如下:
int RepeatNum(int *Array, int N){int i;int TempSum1 = 0, TempSum2 = 0;for(i = 0;i < N;i++){TempSum1 = i + 1;TempSum2+ = Array[i];}return N-(TempSum1 - TempSum2) ;}

        解法二:网上将此种解法称之为标志数组法该解法的核心思想是:申请一个数组长度为N-1的字符串数组Help[N-1],并将该数组的各项均初始为’0‘,然后从头开始遍历数组A[N],取A[i]的值,并将在Help数组上相应的位置,即Help[A[i]-1] = '1',如果该位置已经是置过'1'的话,那么该数就是那个重复的数。该方法的实现代码如下:
int RepeatNum(int *Array, int N){int i;char Help[N-1];for(i = 0;i < N-1;i++){Help[i] = '0';}for(i = 0;i < N;i++){if(Help[A[i]-1] == '1')return A[i];elseHelp[A[i-1]] = '1';}}

        解法三:固定偏移标志法。这种方法是我在网上找的,该解法的核心思想是:利用A[N]本身中值和下标的关系来做标记,处理完成后再清楚标记即可。对于数组A[N],最大的数是N-1,若A[i] = M在某处出现时,将A[M]加一次N,做标记,当某处A[i] = K再次成立时,查看A[K],就可以知道K已经出现过。A[i]在程序中最大的时候是N-1+N=2*N-1,这不是超出范围了吗?不要紧,我们可将它作为一个限制条件。实现代码如下:
int RepeatNum(int *Array,int N){int temp=0;for(int i=0; i<N; i++){if(Array[i] >= N)temp = Array[i]-N; // 该值重复了,因为曾经加过一次了else temp = Array[i];if(Array[temp] < N) {Array[temp]+ = N; //做上标记}else {    return temp; //有重复;}}return -1;//无重复}

             好了,对于情况一的解法暂时就是这些。如果您还有更好的解法请提出来,分享一下。接下来我们开始讨论情况二所对应的相关解法。

        情况二,即这N个数的大小是任意的。在这我给出三种解法,前两种解法都是很基础的。
        解法一:我们可以用两个for循环搞定。很简单,这就像学习最简单的排序算法一样。不过该算法的效率确实太低了,核心思想就不说了,直接看代码吧!
int RepeatNum(int *Array,int N){int i, j;for(i = 0;i < N - 1;i++){for(j = i + 1;j <= N - 1; j++){if(Array[i] == Array[j]){return Array[j];}}}}

            解法二:由于该数组中的元素很有可能是无序的,所以当你拿到这个问题时,解法一通常是先想到的。但是由于解法一的效率不是很好(时间复杂度为O(N*N)),那我们怎么提高呢?OK,我们来看看解法二。解法二的思想很简单,就是先对数组做一次排序,让数组成为有序的,即从小到大或者从大到小。做排序的话,可供我们选择的优秀的排序算法有很多,比如快速排序、堆排序、归并排序等,而且这几种排序算法的时间复杂度都是O(N*LgN),你可以任选一种。排序完成之后,数组A[N]变成有序数组,那么从这个有序数组中找出那个重复的数,问题就好解决了,只需做一次循环就可以解决。具体做法是:从头开始遍历这个有序数组,当前一个数和后一个数相等时就是我们要找的那个重复的数,不相等时只需i++,比较一下组数,知道找出那个重复的数。对于此解法,时间复杂度为O(N*LgN+N)。实现代码如下:

int RepeatNum(int *OrderArray,int N){    int i;    for(i = 0;i < N;i++)    {        if(OrderArray[i] == OrderArray[i+1])            return OrderArray[i];    }}

         上述两种解法,我们应该很容易就能够做到。那么还有没有更好的方法呢?当然有,接下来我们就开始介绍第三种方法。


          解法三:我认为可以用哈希算法来解决这个问题。利用哈希算法,可以建立一种键值与真实值之间的对应关系。每一个真实值只有一个键值,但是每一个键值可以对应多个真实值,这样可以再数组中很快的找到我们想要的数,而且利用哈希算法可以对该问题在时间复杂度上做出很好的优化。关于哈希算法及其原理,我在网上找了一些资料分享一下。


        哈希算法原理:http://wenwen.soso.com/z/q181569235.htm

        哈希算法:http://baike.soso.com/ShowLemma.e?sp=l7892037&ch=w.search.baike.unelite

        著名的哈希算法:http://blog.csdn.net/longronglin/article/details/1782678

        暴雪的哈希算法:http://opaque.blogbus.com/logs/30017835.html


          OK,以上就是对数组中找重复数的几种解法。不过我相信对于这个问题的解法绝不止上述几个,肯定还有更好的解法,欢迎大家分享,共同交流。