1.前言
对于稍微会点算法的而已,这是一个极其容易的问题。但是,我不会,所以记录一个帖子。看了算法相关的书籍,理解了时间复杂度和空间复杂度相关的概念,发现曾经望而却步的东西,当你真正去
做的时候,就发现真是不容易,望而却步还是有原因的,一步一步来。
2.起步
这个问题有2个要求,一是找出重复数字,二是给出重复次数。每一个拆开都可以是一个新的问题,解法也各不相同,先拆分问题看看。
找出重复数字
这个问题在工作也是常见的,最近我在做Excel的数据导入,导入的时候需要判断Excel表单中的关键信息不能重复,如唯一id不能重复。
我之前都是用的哈希Set集合来做的:
Setset = 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(); Mapmap = 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(); Mapmap = 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]; Mapmap = 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 ) ); } }