阿里-马云的学习笔记

导航

面试题:寻找缺失的整数

题目

在一个无序数组里有99个不重复的正整数,范围是1~100,唯独缺少1个1~100中的整数。如何找出这个缺失的整数?

解决方案

解决方法一

创建一个哈希表,以1到100这100个整数为Key。然后遍历整个数组,每读到一个整数,就定位到哈希表中对应的Key,然后删除这个Key。由于数组中缺少1个整数,哈希表最终一定会有99个Key被删除,从而剩下1个唯一的Key。这个剩下的Key就是那个缺失的整数。假设数组长度是n,那么该解法的时间复杂度是O(n),空间复杂度是O(n)。

解决方法二

先把数组元素从小到大进行排序,然后遍历已经有序的数组,如果发现某两个相邻元素并不连续,说明缺少的就是这两个元素之间的整数。假设数组长度是n,如果用时间复杂度为O(nlogn)的排序算法进行排序,那么该解法的时间复杂度是O(nlogn),空间复杂度是O(1)。

解决方法三

先算出1+2+3+…+100的和,然后依次减去数组里的元素,最后得到的差值,就是那个缺失的整数。假设数组长度是n,那么该解法的时间复杂度是O(n),空间复杂度是O(1)。
package arithmetic.com.ty.binary;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;

public class LockData {
    public static void main(String args[])
    {
         //随机生成1-100的一个数作为缺失数
         Random rand = new Random();
         int miss=0;
         miss=rand.nextInt(100)+1;
         System.out.println("缺失的数为"+miss);
         //生成缺失了一个数之后的数组
         int[] missArray = new int[99];
         int j=0;
         for(int i=1;i<=100;i++)
         {
             if(i!=miss)
             {
                  missArray[j] = i;
                  j++;
             }
         }
         System.out.println("解法一:"+solution1(missArray));
         System.out.println("解法二:"+solution2(missArray));
         System.out.println("解法三:"+solution3(missArray));
    }
    /*解法一
     * 创建一个HashMap,以1到100为键,值都是0 。然后遍历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一。
     * 由于数组中缺少一个整数,最终一定有99个键对应的值等于1, 剩下一个键对应的值等于0。遍历修改后的HashMap,找到这个值为0的键。
     * 假设数组长度是N,那么该解法的时间复杂度是O(1),空间复杂度是O(N)。**/
    public static int solution1(int[] missArray)
    {
         int missnumber = 0;
         //初始化map
         HashMap<Integer, Integer> myMap = new HashMap<Integer, Integer>(); 
         for(int i=1;i<=100;i++)
         {
             myMap.put(i, 0);
         }
         //历整个数组,每读到一个整数,就找到HashMap当中对应的键,让其值加一
         for(int m:missArray)
         {
             myMap.remove(m);
         }
         //遍历修改后的HashMap,找到这个值为0的键
         for (int key : myMap.keySet()) {
             if(myMap.get(key)==0)
             {
                  missnumber = key;
                  break;
             }
          }
         return missnumber;
    }
    /*
     * 解法二:
     * 先把数组元素进行排序,然后遍历数组,检查任意两个相邻元素数值是否是连续的。如果不连续,则中间缺少的整数就是所要寻找的;如果全都连续,则缺少的整数不是1就是100。
     * 假设数组长度是N,如果用时间复杂度为O(N*LogN)的排序算法进行排序,那么该解法的时间复杂度是O(N*LogN),空间复杂度是O(1)。
     */
    public static int solution2(int[] missArray)
    {
         int missnumber = 0;
         //数组排序
         Arrays.sort(missArray);
         //检查是否相邻
         for(int i=0;i<missArray.length-1;i++)
         {
             if(missArray[i+1]-missArray[i]!=1)
             {
                  missnumber=missArray[i]+1;
             }
         }
         //如果检查到相邻则返回否则缺失的数为100
         if(missnumber!=0)
         {
             return missnumber;
         }
         else {
             return 100;
         }
    }
    /*
     * 解法三:
     * 很简单也很高效的方法,先算出1+2+3....+100的合,然后依次减去数组里的元素,最后得到的差,就是唯一缺失的整数。
     * 假设数组长度是N,那么该解法的时间复杂度是O(N),空间复杂度是O(1)。
     */
    public static int solution3(int[] missArray)
    {
         int missnumber=0;
         int all = 0;
         int missall = 0;
         for(int i=1;i<=100;i++)
         {
             all = all+i;    
         }
         for(int m:missArray)
         {
             missall = missall+m;
         }
         return all-missall;
    }
}

问题扩展

题目第1次扩展:
一个无序数组里有若干个正整数,范围是1~100,其中99个整数都出现了偶数次,只有1个整数出现了奇数次,如何找到这个出现奇数次的整数?

首先得知道异或运算:

相同位得0,不同位得1。如果一个数出现了偶数次,比如2次、4次。。。那么所有结果就变成0了,只有奇数次的整数会被留下。

代码:

     /*
      * 遍历整个数组,依次做异或运算。由于异或在位运算时相同为0,不同为1,因此所有出现偶数次的整数都会相互抵消变成0,只有唯一出现奇数次的整数会被留下。
      * 假设数组长度是N,那么该解法的时间复杂度是O(N),空间复杂度是O(1)。**/
     public static int solution2(int[] missArray)
     {
          int missnumber = 0;
          for(int m:missArray)
          {
              missnumber = missnumber^m;
          }
          return missnumber;
     }
题目第2次扩展:
假设一个无序数组里有若干个正整数,范围是1~100,其中有98个整数出现了偶数次,只有2个整数出现了奇数次,如何找到这2个出现奇数次的整数?

思路:

把2个出现了奇数次的整数命名为A和B。遍历整个数组,然后依次做异或运算,进行异或运算的最终结果,等同于A和B进行异或运算的结果。在这个结果中,至少会有一个二进制位是1(如果都是0,说明A和B相等,和题目不相符)。举个例子,给出一个无序数组{4,1,2,2,5,1,4,3},所有元素进行异或运算的结果是00000110B。

选定该结果中值为1的某一位数字,如00000110B的倒数第2位是1,这说明A和B对应的二进制的倒数第2位是不同的。其中必定有一个整数的倒数第2位是0,另一个整数的倒数第2位是1。根据这个结论,可以把原数组按照二进制的倒数第2位的不同,分成两部分,一部分的倒数第2位是0,另一部分的倒数第2位是1。由于A和B的倒数第2位不同,所以A被分配到其中一部分,B被分配到另一部分,绝不会出现A和B在同一部分,另一部分既没有A,也没有B的情况。

 

 

代码:

    public static void solution2(int[] missArray) {
         int temp = 0;
         //数组所有元素异或。数组所有元素异或结果其实就是两个奇数次整数的异或结果,因为一个整数出现偶数次,异或一定是0.
         for(int m:missArray) {
             temp = temp^m;
         }
         
         int site=1;
         /*
          * 找到1位为1的元素。
          * 例如无序数组{4,1,2,2,5,1,4,3},异或后的temp=00000110B。
          * 1<<site & temp == 0则代表第site位都是0,若果不等于0,说明找到1了。site就是1的位数
          */
         while((temp & (1 << site))==0) {
             site++;
         }
         
         int num1 = 0;
         int num2 = 0;
         for(int m:missArray) {
             //根据上面获得到的第site位为1的情况,取第site位为1的所有数,循环做异或,另外一组做异或。保证A、B是分开的
             if(getBit(m, site)) {
                  num1 = num1^m;
             }
             
             else {
                  num2 = num2^m;
             }
         }
         System.out.println("奇数的数为"+num1);
         System.out.println("奇数的数为"+num2);
    }
    
    //获取 整数 num 的第 i 位的值
    private static boolean getBit(int num, int i) {
        //true 表示第i位为1,否则为0。a & b == 1 说明a b都是1
        return ((num & (1 << i)) != 0);
    }

 

posted on 2020-04-28 14:03  阿里-马云的学习笔记  阅读(913)  评论(0编辑  收藏  举报