问题:找到一组数字中的重复数字,并给出该数字重复的次数。

1.前言 

  对于稍微会点算法的而已,这是一个极其容易的问题。但是,我不会,所以记录一个帖子。看了算法相关的书籍,理解了时间复杂度和空间复杂度相关的概念,发现曾经望而却步的东西,当你真正去

做的时候,就发现真是不容易,望而却步还是有原因的,一步一步来。

 

2.起步

  这个问题有2个要求,一是找出重复数字,二是给出重复次数。每一个拆开都可以是一个新的问题,解法也各不相同,先拆分问题看看。

   找出重复数字

       这个问题在工作也是常见的,最近我在做Excel的数据导入,导入的时候需要判断Excel表单中的关键信息不能重复,如唯一id不能重复。

     我之前都是用的哈希Set集合来做的:

        Set set = new HashSet<>();for( int i = 0; i < maths.length; i ++ ) {
            //如果重复出现,则无法加入set集合
            if( ! set.add( maths[i] )) {
                
            }

    而后我也百度了一下相关的问题,在牛客中也发现了类似的算法题目:

              链接:https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8
              来源:牛客网

    在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。

    也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

    当然也给出时间复杂度和空间复杂度的限制:时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 32M,其他语言64M。

    选取了热度最高的两个答案,用java自己写了一边,其余的可以自己点击链接去学习。

         1.不需要额外的空间消耗,时间效率是O(n)

    /**
     * 不需要额外的数组或者hash table来保存,
     * 题目里写了数组里数字的范围保证在0 ~ n-1 之间,所以可以利用现有数组设置标志,
     * 当一个数字被访问过后,可以设置对应位上的数 + n,
     * 之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。
     * @param numbers 数组
     * @param length 长度
     * @return
     */
    public int findDup( int[] numbers , int length ) {
        for (int i = 0; i < numbers.length; i++) {
            //获取到下标为i的数字 index
            int index = numbers[ i ];
            //判断数字是否溢出,不能大于length,因为需要将index作为下标
            if ( index >= length ){
                index -= length;
            }
            //出现比数组长度还大的值,就说明之前出现过 
            if( numbers[index] >= length ) {
                return index;
            }
            //将index作为下标位置的值加上长度 下次遇到大于length的值时候 直接返回index就行
            //存在数字过大 溢出问题 
            numbers[index] = numbers[index] + length;
        }
        return -1;
    }

    2.在这个算法下面对于复杂度的讨论就神仙打架了,摘取了有用的信息:

    (1)boolean不是占1位,计算机处理处理数据的最小单元是1字节,一般1位的话,其余7位会被0补齐。
    (2)在java虚拟机规范中,JVM没有用于操作boolean的字节码指令,在编译后用int的数据类型代替boolean,此时boolean占4字节。
    (3)boolean[]数组编译后会被byte[]数组代替,此时的boolean占1字节。
    总结:boolean单独存在占4字节,在boolean[]中占1字节。

            /**
             * boolean只占一位,所以还是比较省的
             * 遍历数组每一个数字,给以该数字为下标的数组值赋true
             * @param numbers 数组
             * @param length 长度
             * @param duplication 数组存放重复的数字
             * @return
             */
            public boolean duplicate( int numbers[], int length, int[] duplication ) {
                boolean[] k = new boolean[length];
                for ( int i = 0; i < k.length; i++ ) {
                    if ( k[numbers[i]] == true ) {
                        duplication[0] = numbers[i];
                        return true;
                    }
                    k[numbers[i]] = true;
                }
                return false;
            }

3.正文    

    1.先用最复杂的双层for循环做把,时间复杂度n2。

    虽然这样的作法复杂,但是逻辑上很好理解。

    /**
     * 双层for循环获取
     * 时间复杂度 O = n * n
     */
    public void getSame( Integer[] maths ){
        Long startTime = System.currentTimeMillis();
        Map map = new HashMap<>();
        for( int i = 0; i < maths.length; i ++ ) {
            //计数器 计算重复的次数
            int count = 0;
            for( int j = 0 ; j < maths.length; j ++ ) {
                if ( maths[i] == maths[j] ) {
                    count ++;
                }
                //除去与本身比较
                if ( count > 1) {
                    map.put( maths[i], count - 1);
                }
            }
        }
        Long endTime = System.currentTimeMillis();
        System.out.println( "消耗的时间:" + ( endTime - startTime ));
        for( int i : map.keySet() ) {
            System.out.println( i + "重复,重复次数为:" + map.get( i ) );
        }

    }

     2.利用hashSet进行判断是否存在,时间复杂度n。

    /**
     * HashSet 去重
     * @param args
     */
    public void HashSetGet( Integer[] maths ) {
        Long startTime = System.currentTimeMillis();
        Map map = new HashMap<>();
    
        Set set = new HashSet<>();
        for( int i = 0; i < maths.length; i ++ ) {
            if ( map.get(maths[i]) == null ) {
                map.put( maths[i] , 0 );
            }
            //如果重复出现,则无法加入set集合
            if( ! set.add( maths[i] )) {
                map.put( maths[i], map.get( maths[i] ) + 1);
            }
        }
        Long endTime = System.currentTimeMillis();
        System.out.println( "消耗的时间:" + ( endTime - startTime ));
        for( int i : map.keySet() ) {
            System.out.println( i + "重复,重复次数为:" + map.get( i ) );
        }
    }

    3.利用boolean进行判断是否存在,时间复杂度n,但是空间复杂度取决于最大数字。

            /**
             * boolean[]判断是否存在重复
             * @param numbers
             * @param length
             * @param max
             */
            public void duplicateCount( int numbers[], int length, int max ) {
                //规定范围
                boolean[] k = new boolean[max];
                Map map = new HashMap<>();
                for ( int i = 0; i < length; i++ ) {
                    if( map.get( numbers[i] ) == null ) {
                        map.put( numbers[i] , 0 );
                    }
                    if ( k[ numbers[i] ] == true ) {
                        map.put( numbers[i] , map.get( numbers[i] ) + 1 );
                    }
                    k[numbers[i]] = true;
                }
                for( int i : map.keySet() ) {
                    System.out.println( i + "重复,重复次数为:" + map.get( i ) );
                }
            }

 

   

你可能感兴趣的:(问题:找到一组数字中的重复数字,并给出该数字重复的次数。)