问题1:1-N的自然数中,少了一个,找出这个数
(1)求和-容易溢出
Sum=1+2+...+N=(N+1)N/2,然后遍历数列每次从S1中减去当前的数字,最后剩下的数字就是所求。
为了防止溢出我们可以每次在S1大于一定的数字后,就去减,然后继续求和,再大于就继续减,以此类推。
public int find_Miss_Number_By_Sum(int array[], int n) { int sum = 0; if (array.length != n - 1) throw new IllegalArgumentException("数组内的自然数目少于n-1"); for (int i : array) { sum += i; } return n * (n + 1) / 2 - sum; }
(2)异或^
Y=1^2^3...^N,然后遍历数列每次异或当前的数字,最后剩下的就是要求的数字
任何数异或自己都等于0,任何数异或0都等于他自己
public int find_Miss_Number_By_XOR(int array[], int n) { int result = 0; if (array.length != n - 1) throw new IllegalArgumentException("数组内的自然数目少于n-1"); for (int i = 0; i < array.length; i++) { result = result ^ array[i]; } for (int i = 0; i < n; i++) { result=result^(i+1); } return result; }
(3)O(N)时间的移动-排序
将a[i]移动到b[a[i]],使得数组有序,然后找出空着的位置
public int find_Miss_Number_By_Sort(int array[], int n) { int result = 0; if (array.length != n - 1) throw new IllegalArgumentException("数组内的自然数目少于n-1"); int b[] = new int[n]; for (int i = 0; i < array.length; i++) { b[array[i] - 1] = array[i]; } for (int i = 0; i < b.length; i++) { if (b[i] == 0) { result = i + 1; break; } } return result; }
(4)O(NlogN)时间的移动-排序
用快排的思想,在1-N中选取游标X对数组快排一次,如果X被放在a[X-1]的位置上那么,要找的数字在X-N之间
否则X被放在a[X-2]的位置上 要找的数字在1-X-1之间 递归求解,直到找的要找的数字。
问题2:1-N个自然数,少了两个,找出这两个数
(1)求和-容易溢出
S1=1+2+...+N=(N+1)N/2
S2=12+22+...+N2=(N+1)(2N+1)N/6
import junit.framework.TestCase; public class Find_Miss_Numbers_By_Sum_And_Mul extends TestCase { //1-N的自然数中,少了一个,找出这个数 //方法:求和,求积 x1+x2 和 x1*x2都知道了以后,求出x1和x2 public void find_Miss_Number_By_Sum_and_Product(Integer array[]) { int sum = 0; //保存缺少的两个数的和 int product = 1;//保存缺少的两个数的积 int n = array.length + 2; // 1-n的自然数的和减去数组中所有元素的和,即为缺少的两个数之和 for (Integer i : array) { sum += i; } sum = n * (n + 1) / 2 - sum; // 1-n的自然数的积除以数组中所有元素,即为缺少的两个数之积 for (int j = 1; j < n + 1; j++) { product = product * j; } for (Integer integer : array) { product /= integer; } System.out.println("数组元素的和为" + sum); System.out.println("数组元素的积为" + product); System.out.println("缺少的第一个数为" + getSolution_left(sum, product)); System.out.println("缺少的第二个数为" + getSolution_right(sum, product)); } // 根据x1+x2 和 x1*x2都x1和x2 public int getSolution_left(int sum, int product) { return (int)(sum - Math.sqrt(sum * sum - 4 * product)) / 2; } public int getSolution_right(int sum, int product) { return (int) (sum + Math.sqrt(sum * sum - 4 * product)) / 2; } public void test() { Integer a[] = { 1, 2, 3, 5, 7, 8, 9 }; find_Miss_Number_By_Sum_and_Product(a); } }
对于少了K个数的情况,如果K很少,我们可以找出K个和上面类似的函数,计算总体值,然后用解K元一次方程得到结果,但要注意函数的选择
(2)异或
按照上面同样的方法,求出最后的值P等于两个数的异或
确定P从低位到高位的第一个1是第i位,现在用快排的思想,将数列分成两个区间A和B,其中A中第i位是0,B中的第i位是1,然后1-N中第i位是0的异或A,最后的结果就为缺少的第一个数。同理在B中求出第二个数(也可在求出第一个数后,用该数异或P,结果即为缺少的第二个数)。
import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; public class Find_Miss_Numbers_By_Xor extends TestCase { /** * @param array 包含所有整数的数组 * @param subArray 缺少整数的数组 * @return 缺少的整数的异或值 */ public static int find_Miss_Number_By_XOR(Integer array[],Integer subArray[]) { int result = 0; for (Integer e : array) { result ^= e; } for (Integer e : subArray) { result ^= e; } return result; } // 获取最低位1的位置 public static int get_mid_By_xor_result(int number) { int location = 0; while (number % 2 == 0) { location++; number /= 2; } return location; } //返回一个数组中第i位为1的所有的数构成的数组 private static Integer[] divid_Array(Integer array[], int i) { List<Integer> list = new ArrayList<Integer>(); for (Integer e : array) { int temp = e; for (int j = 0; j < i; j++) { temp = e / 2; } if (temp % 2 == 1) { list.add(e); } } Integer[] result = new Integer[list.size()]; for (int j = 0; j < list.size(); j++) { result[j] = list.get(j); } return result; } /** * @param array 包含所有整数的数组 * @param subArray 缺少整数的数组 */ public static void getMissNumber(Integer array[], Integer subArray[]) { int xor = find_Miss_Number_By_XOR(array, subArray); System.out.println("异或的结果为" + xor); int mid = get_mid_By_xor_result(xor); System.out.println("最低位1的位置" + mid); // 数组A的元素为: System.out.println("数组A的元素为:"); Integer[] array1 = divid_Array(array, mid); for (Integer e : array1) { System.out.print(e + "、"); } // 数组B的元素为: System.out.println(); System.out.println("数组B的元素为:"); Integer[] array2 = divid_Array(subArray, mid); for (Integer e : array2) { System.out.print(e + "、"); } System.out.println(); System.out.println("缺少的第一个数为:"); int solution1 = find_Miss_Number_By_XOR(array1, array2); System.out.println(solution1); System.out.println("缺少的第二个数为:"); int solution2 = solution1 ^ xor; System.out.println(solution2); } public static void main(String[] args) { Integer array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12}; Integer subArray[] = { 1, 2, 4, 5, 6, 7, 8, 9,11,12 }; getMissNumber(array, subArray); } }
(3)O(N)时间移动-排序
跟上面一样,实际上这种方法对于少了K个数的情况都能适用。
import junit.framework.TestCase; public class Find_Miss_Numbers_By_Move extends TestCase { // 1-N的自然数中,少了一个,找出这个数 // 方法:将a[i]移动到a[a[i]],使得数组有序,然后找出空着的位置 public void find_Miss_Number_By_Move(int array[], int n) { int b[] = new int[n]; for (int i : array) { b[i - 1] = i; } System.out.print("数组的元素为:"); for (int j : b) { if (j != 0) { System.out.print(j + "、"); } } System.out.println(); System.out.print("缺少的数为:"); for (int k = 0; k < b.length; k++) { if (b[k] == 0) { System.out.print(k + 1 + "、"); } } } public void test() { int a[] = { 1, 2, 4, 7, 8, 9 }; find_Miss_Number_By_Move(a, 9); } }
(4)O(NlogN)时间移动-排序
跟上面的方法一样
如果X被放在a[X-1]位置上,要找的两个数字在X-N之间
如果X被放在a[X-2]位置上,要找的数字一个在1-X-1间,一个在X-N之间
如果X被放在a[X-3]位置上,要找的数字都在1-X-1间
对于少了K个数字的情况,这种方法也可以做,但实现起来就比较复杂了
问题3:给你n个数,其中有且仅有一个数出现了奇数次,其余的数都出现了偶数次。用线性时间常数空间找出出现了奇数次的那一个数。
(1)异或
一个数跟自己偶数次异或是0,奇数次异或是自己
问题4:给你n个数,其中有且仅有两个数出现了奇数次,其余的数都出现了偶数次。用线性时间常数空间找出出现了奇数次的那两个数。
从头到尾异或一遍,你就得到了需要求的两个数异或后的值。这两个数显然不相等,异或出来的结果不为0。我们可以据此找出两个数的二进制表达中不同的一位,然后把所有这n个数分成两类,在那一位上是0的分成一类,在那一位上是1的分到另一类。对每一类分别使用前一个问题的算法。
代码和异或找两个数一样,将测试数据该成即可
public static void main(String[] args) { Integer array[] = { 1,1,2, 2, 3,3,4, 4, 5,5,}; Integer subArray[] = { 1, 2,2, 3,4, 4, 5,5,}; getMissNumber(array, subArray); }