题目:一个无序数组里有99个不重复正整数,范围从1到100,唯独缺少一个整数。如何找出这个缺失的整数?
解法一:
创建一个HashMap,以1到100为键,值都是0 。然后遍历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一。
由于数组中缺少一个整数,最终一定有99个键对应的值等于1, 剩下一个键对应的值等于0。遍历修改后的HashMap,找到这个值为0的键。
假设数组长度是N,那么该解法的时间复杂度是O(1),空间复杂度是O(N)。
解法二:
先把数组元素进行排序,然后遍历数组,检查任意两个相邻元素数值是否是连续的。如果不连续,则中间缺少的整数就是所要寻找的;如果全都连续,则缺少的整数不是1就是100。
假设数组长度是N,如果用时间复杂度为O(N*LogN)的排序算法进行排序,那么该解法的时间复杂度是O(N*LogN),空间复杂度是O(1)。
解法三:
很简单也很高效的方法,先算出1+2+3....+100的合,然后依次减去数组里的元素,最后得到的差,就是唯一缺失的整数。
假设数组长度是N,那么该解法的时间复杂度是O(N),空间复杂度是O(1)。
1 java代码如下 2 3 import java.util.Arrays; 4 import java.util.HashMap; 5 import java.util.Random; 6 //寻找缺失的整数 7 //一个无序数组里有99个不重复正整数,范围从1到100,唯独缺少一个整数。如何找出这个缺失的整数 8 public class LackData { 9 public static void main(String args[]) 10 { 11 //随机生成1-100的一个数作为缺失数 12 Random rand =new Random(); 13 int miss=0; 14 miss=rand.nextInt(100)+1; 15 System.out.println("缺失的数为"+miss); 16 //生成缺失了一个数之后的数组 17 int[] missArray = new int[99]; 18 int j=0; 19 for(int i=1;i<=100;i++) 20 { 21 if(i!=miss) 22 { 23 missArray[j] = i; 24 j++; 25 } 26 } 27 System.out.println("解法一:"+solution1(missArray)); 28 System.out.println("解法二:"+solution2(missArray)); 29 System.out.println("解法三:"+solution3(missArray)); 30 } 31 /*解法一 32 * 创建一个HashMap,以1到100为键,值都是0 。然后遍历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一。 33 * 由于数组中缺少一个整数,最终一定有99个键对应的值等于1, 剩下一个键对应的值等于0。遍历修改后的HashMap,找到这个值为0的键。 34 * 假设数组长度是N,那么该解法的时间复杂度是O(1),空间复杂度是O(N)。**/ 35 public static int solution1(int[] missArray) 36 { 37 int missnumber = 0; 38 //初始化map 39 HashMap<Integer, Integer> myMap = new HashMap<Integer, Integer>(); 40 for(int i=1;i<=100;i++) 41 { 42 myMap.put(i, 0); 43 } 44 //历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一 45 for(int m:missArray) 46 { 47 myMap.put(m, myMap.get(m)+1); 48 } 49 //遍历修改后的HashMap,找到这个值为0的键 50 for (int key : myMap.keySet()) { 51 if(myMap.get(key)==0) 52 { 53 missnumber = key; 54 break; 55 } 56 } 57 return missnumber; 58 } 59 /* 60 * 解法二: 61 * 先把数组元素进行排序,然后遍历数组,检查任意两个相邻元素数值是否是连续的。如果不连续,则中间缺少的整数就是所要寻找的;如果全都连续,则缺少的整数不是1就是100。 62 * 假设数组长度是N,如果用时间复杂度为O(N*LogN)的排序算法进行排序,那么该解法的时间复杂度是O(N*LogN),空间复杂度是O(1)。 63 */ 64 public static int solution2(int[] missArray) 65 { 66 int missnumber = 0; 67 //数组排序 68 Arrays.sort(missArray); 69 //检查是否相邻 70 for(int i=0;i<missArray.length-1;i++) 71 { 72 if(missArray[i+1]-missArray[i]!=1) 73 { 74 missnumber=missArray[i]+1; 75 } 76 } 77 //如果检查到相邻则返回否则缺失的数为100 78 if(missnumber!=0) 79 { 80 return missnumber; 81 } 82 else { 83 return 100; 84 } 85 } 86 /* 87 * 解法三: 88 * 很简单也很高效的方法,先算出1+2+3....+100的合,然后依次减去数组里的元素,最后得到的差,就是唯一缺失的整数。 89 * 假设数组长度是N,那么该解法的时间复杂度是O(N),空间复杂度是O(1)。 90 */ 91 public static int solution3(int[] missArray) 92 { 93 int missnumber=0; 94 int all = 0; 95 int missall = 0; 96 for(int i=1;i<=100;i++) 97 { 98 all = all+i; 99 } 100 for(int m:missArray) 101 { 102 missall = missall+m; 103 } 104 return all-missall; 105 } 106 }
题目扩展:一个无序数组里有若干个正整数,范围从1到100,其中99个整数都出现了偶数次,只有一个整数出现了奇数次(比如1,1,2,2,3,3,4,5,5),如何找到这个出现奇数次的整数?
解法一:
创建一个HashMap,以1到100为键,值都是0 。然后遍历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一。由于数组中缺少一个整数,最终一定有99个键对应的值为偶数, 剩下一个键对应的值为奇数。遍历修改后的HashMap,找到这个值为奇数 的键。假设数组长度是N,那么该解法的时间复杂度是O(1),空间复杂度是O(N)。
解法二 遍历整个数组,依次做异或运算。由于异或在位运算时相同为0,不同为1,因此所有出现偶数次的整数都会相互抵消变成0,只有唯一出现奇数次的整数会被留下。假设数组长度是N,那么该解法的时间复杂度是O(N),空间复杂度是O(1)。
1 import java.util.HashMap; 2 import java.util.Random; 3 import javax.xml.transform.Templates; 4 //题目扩展:一个无序数组里有若干个正整数,范围从1到100,其中99个整数都出现了偶数次,只有一个整数出现了奇数次(比如1,1,2,2,3,3,4,5,5),如何找到这个出现奇数次的整数? 5 public class LackDataExtension { 6 public static void main(String args[]) 7 { 8 //随机生成1-100的一个数作为出现基数次的数 9 Random rand =new Random(); 10 int miss=0; 11 miss=rand.nextInt(100)+1; 12 System.out.println("缺失的数为"+miss); 13 //存储每个数出现的次数 key为数value为该数出现的次数 14 HashMap<Integer, Integer> store = new HashMap<Integer,Integer>(); 15 int number = 0; 16 for(int i=1;i<=100;i++) 17 { 18 int temp = 0; 19 if(i!=miss) 20 { 21 temp = randEven(); 22 store.put(i, temp); 23 number = number+temp; 24 } 25 else 26 { 27 temp = randOdd(); 28 store.put(i, temp); 29 number = number+temp; 30 } 31 } 32 //生成缺99个整数都出现了偶数次,只有一个整数出现了奇数次的数组 33 int[] missArray = new int[number]; 34 int temp=0; 35 for(int i=1;i<=100;i++) 36 { 37 for(int j=0;j<store.get(i);j++) 38 { 39 missArray[temp] = i; 40 temp++; 41 } 42 } 43 System.out.println("解法一:"+solution1(missArray)); 44 System.out.println("解法二:"+solution2(missArray)); 45 } 46 /* 47 * 解法1 48 * 创建一个HashMap,以1到100为键,值都是0 。然后遍历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一。 49 * 由于数组中缺少一个整数,最终一定有99个键对应的值为偶数, 剩下一个键对应的值为奇数。遍历修改后的HashMap,找到这个值为奇数的键。 50 * 假设数组长度是N,那么该解法的时间复杂度是O(1),空间复杂度是O(N)。**/ 51 public static int solution1(int[] missArray) 52 { 53 int missnumber = 0; 54 //初始化map 55 HashMap<Integer, Integer> myMap = new HashMap<Integer, Integer>(); 56 for(int i=1;i<=100;i++) 57 { 58 myMap.put(i, 0); 59 } 60 //历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一 61 for(int m:missArray) 62 { 63 myMap.put(m, myMap.get(m)+1); 64 } 65 //遍历修改后的HashMap,找到这个值为0的键 66 for (int key : myMap.keySet()) { 67 if(myMap.get(key)%2==1) 68 { 69 missnumber = key; 70 break; 71 } 72 } 73 return missnumber; 74 } 75 /* 76 * 解法2 77 * 遍历整个数组,依次做异或运算。由于异或在位运算时相同为0,不同为1,因此所有出现偶数次的整数都会相互抵消变成0,只有唯一出现奇数次的整数会被留下。 78 * 假设数组长度是N,那么该解法的时间复杂度是O(N),空间复杂度是O(1)。**/ 79 public static int solution2(int[] missArray) 80 { 81 int missnumber = 0; 82 for(int m:missArray) 83 { 84 missnumber = missnumber^m; 85 } 86 return missnumber; 87 } 88 //生成1-10的奇数 89 public static int randOdd() 90 { 91 Random rand =new Random(); 92 int temp = rand.nextInt(5)+1; 93 return temp*2-1; 94 } 95 //生成1-10的偶数 96 public static int randEven() 97 { 98 Random rand =new Random(); 99 int temp = rand.nextInt(5)+1; 100 return temp*2; 101 } 102 }
题目第二次扩展:一个无序数组里有若干个正整数,范围从1到100,其中98个整数都出现了偶数次,只有两个整数出现了奇数次(比如1,1,2,2,3,4,5,5),如何找到这个出现奇数次的整数?
解法:
遍历整个数组,依次做异或运算。由于数组存在两个出现奇数次的整数,所以最终异或的结果,等同于这两个整数的异或结果。这个结果中,至少会有一个二进制位是1(如果都是0,说明两个数相等,和题目不符)。
举个例子,如果最终异或的结果是5,转换成二进制是00000101。此时我们可以选择任意一个是1的二进制位来分析,比如末位。把两个奇数次出现的整数命名为A和B,如果末位是1,说明A和B转为二进制的末位不同,必定其中一个整数的末位是1,另一个整数的末位是0。
根据这个结论,我们可以把原数组按照二进制的末位不同,分成两部分,一部分的末位是1,一部分的末位是0。由于A和B的末位不同,所以A在其中一部分,B在其中一部分,绝不会出现A和B在同一部分,另一部分没有的情况。
这样一来就简单了,我们的问题又回归到了上一题的情况,按照原先的异或解法,从每一部分中找出唯一的奇数次整数即可。
假设数组长度是N,那么该解法的时间复杂度是O(N)。把数组分成两部分,并不需要借助额外存储空间,完全可以在按二进制位分组的同时来做异或运算,所以空间复杂度仍然是O(1)。
1 import java.util.HashMap; 2 import java.util.Random; 3 import javax.xml.transform.Templates; 4 //题目扩展:一个无序数组里有若干个正整数,范围从1到100,其中98个整数都出现了偶数次,只有两个整数出现了奇数次(比如1,1,2,2,3,4,5,5),如何找到这个出现奇数次的整数? 5 public class LackDataExtensionTwo { 6 public static void main(String args[]) 7 { 8 //随机生成1-100的两个数作为出现奇数次的数 9 Random rand =new Random(); 10 int miss=0; 11 miss=rand.nextInt(100)+1; 12 System.out.println("奇数次的数1为"+miss); 13 int miss1=0; 14 do 15 { 16 miss1=rand.nextInt(100)+1; 17 } 18 while(miss1!=0&&miss1==miss); 19 System.out.println("奇数次的数2为"+miss1); 20 //存储每个数出现的次数 key为数value为该数出现的次数 21 HashMap<Integer, Integer> store = new HashMap<Integer,Integer>(); 22 int number = 0; 23 for(int i=1;i<=100;i++) 24 { 25 int temp = 0; 26 if(i!=miss&&i!=miss1) 27 { 28 temp = randEven(); 29 store.put(i, temp); 30 number = number+temp; 31 } 32 else 33 { 34 temp = randOdd(); 35 store.put(i, temp); 36 number = number+temp; 37 } 38 } 39 //生成缺98个整数都出现了偶数次,只有二个整数出现了奇数次的数组 40 int[] missArray = new int[number]; 41 int temp=0; 42 for(int i=1;i<=100;i++) 43 { 44 for(int j=0;j<store.get(i);j++) 45 { 46 missArray[temp] = i; 47 temp++; 48 } 49 } 50 System.out.print("解法一:"); 51 solution1(missArray); 52 System.out.print("解法二:"); 53 solution2(missArray); 54 } 55 /* 56 * 解法1 57 * 创建一个HashMap,以1到100为键,值都是0 。然后遍历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一。 58 * 由于数组中缺少一个整数,最终一定有98个键对应的值为偶数, 剩下二个键对应的值为奇数。遍历修改后的HashMap,找到这个值为奇数的键。 59 * 假设数组长度是N,那么该解法的时间复杂度是O(1),空间复杂度是O(N)。**/ 60 public static void solution1(int[] missArray) 61 { 62 int missnumber = 0; 63 //初始化map 64 HashMap<Integer, Integer> myMap = new HashMap<Integer, Integer>(); 65 for(int i=1;i<=100;i++) 66 { 67 myMap.put(i, 0); 68 } 69 //历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一 70 for(int m:missArray) 71 { 72 myMap.put(m, myMap.get(m)+1); 73 } 74 //遍历修改后的HashMap,找到这个值为0的键 75 for (int key : myMap.keySet()) { 76 if(myMap.get(key)%2==1) 77 { 78 missnumber = key; 79 System.out.println("奇数的数为"+missnumber); 80 } 81 } 82 } 83 /* 84 * 解法2 85 * 遍历整个数组,依次做异或运算。由于数组存在两个出现奇数次的整数,所以最终异或的结果,等同于这两个整数的异或结果。这个结果中,至少会有一个二进制位是1(如果都是0,说明两个数相等,和题目不符)。 86 * 举个例子,如果最终异或的结果是5,转换成二进制是00000101。此时我们可以选择任意一个是1的二进制位来分析,比如末位。 87 * 把两个奇数次出现的整数命名为A和B,如果末位是1,说明A和B转为二进制的末位不同,必定其中一个整数的末位是1,另一个整数的末位是0。 88 * 根据这个结论,我们可以把原数组按照二进制的末位不同,分成两部分,一部分的末位是1,一部分的末位是0。 89 * 由于A和B的末位不同,所以A在其中一部分,B在其中一部分,绝不会出现A和B在同一部分,另一部分没有的情况。 90 * 这样一来就简单了,按照原先的异或解法,从每一部分中找出唯一的奇数次整数即可。 91 * 假设数组长度是N,那么该解法的时间复杂度是O(N)。把数组分成两部分,并不需要借助额外存储空间,完全可以在按二进制位分组的同时来做异或运算,所以空间复杂度仍然是O(1)。 92 **/ 93 public static void solution2(int[] missArray) 94 { 95 int temp = 0; 96 //数组所有元素异或 97 for(int m:missArray) 98 { 99 temp = temp^m; 100 } 101 int site=1; 102 //找到1位为1的元素 103 while((temp & (1 << site))==0) 104 { 105 site++; 106 } 107 int num1 = 0; 108 int num2 = 0; 109 for(int m:missArray) 110 { 111 if(getBit(m, site)) 112 { 113 num1 = num1^m; 114 } 115 else { 116 num2 = num2^m; 117 } 118 } 119 System.out.println("奇数的数为"+num1); 120 System.out.println("奇数的数为"+num2); 121 } 122 //生成1-10的奇数 123 public static int randOdd() 124 { 125 Random rand =new Random(); 126 int temp = rand.nextInt(5)+1; 127 return temp*2-1; 128 } 129 //生成1-10的偶数 130 public static int randEven() 131 { 132 Random rand =new Random(); 133 int temp = rand.nextInt(5)+1; 134 return temp*2; 135 } 136 //获取 整数 num 的第 i 位的值 137 private static boolean getBit(int num, int i) 138 { 139 return ((num & (1 << i)) != 0);//true 表示第i位为1,否则为0 140 } 141 }