异或运算运用

异或运算

概念:

  • 异或运算:相同为0,不同为1
  • 同或运算:相同以1,不同为0
  • 总结:异或运算 = 无进位相加

性质:

  • 0^N == N
  • N^N == 0
  • 满足交换律和结合律
  • 如何不用额外变量交换两个数

    灵活运用异或的两个性质,如果是数组交换的话必须保证互换的两个索引值不相同

    public static void main(String[] args) {
        int a = 19;
        int b = 6000;
        System.out.println(a);
        System.out.println(b);
        // 不用额外变量交换两个数
        // 异或:不进位相加
        a = a ^ b;
        b = a ^ b; // b = a ^ b ^ b = a ^ 0 = a
        a = a ^ b; // a = a ^ b ^ a = 0 ^ b = b
    
        System.out.println(a);
        System.out.println(b);
    }
    
    // 注意: i 和 j不能相同,如果相同的话就全是0了
    public void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    
    }
    
  • 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数

    定义返回结果ans,遍历数组进行异或则可得到出现奇数次的数

    // 原理: 使用的也是异或的性质 例: 11 3333 55 66 7777 8
    // 只要是偶数个,那么就为0,如果只有一个奇数则说明其他的都是偶数,那么他们的异或最终为0然后再和奇数异或,最终得到这个奇数
    public static int printOddTimesNum1(int[] arr) {
        int ans = 0;
        for (int i = 0; i < arr.length; i++) {
            ans ^= arr[i];
        }
        return ans;
    }
    
  • 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数

    • 遍历数组进行异或获得最终结果,也就是a^b

    • 算出a^b这个数的最右侧的1

      (a&(~a+1),意味着a,b在这一位是不同的)

    • 遍历数组,判断当前数是否有最右侧的数,如果有则进行异或处理,最终得到的则是这两个数中的一个

    • 得到的数与a^b进行异或则获得另外一个数

      public static void prinOddTimesNum2(int[] arr) {
          int eor = 0;
          for (int i = 0; i < arr.length; i++) {
              eor ^= arr[i];
          }
          // 最终res = a ^ b,因为a,b是两种数所以eor != 0
          // 取res最右侧的1,代表着a,b的这一位是不相同的因为异或不同才为1
          // 例: 00110010110111000
          // rightOne: 00000000000001000
          int rightOne = eor & (~eor + 1);
      
          // 这里就将a,b分离开了
          int onlyOne = 0;
          for (int i = 0; i < arr.length; i++) {
              // arr[1] = 111100011110000
              // rightOne= 000000000010000
              // 如果数组中的数的最右侧这一位不存在则进行异或,不存在就不异或
              // 存在 一个奇数 偶数....所以异或之后就是其中一个奇数
              if ((arr[i] & rightOne) != 0) {
                  onlyOne ^= arr[i];
              }
          }
          System.out.println(onlyOne + " " + (eor ^ onlyOne));
      }
      
  • 一个数组中有一种数出现K次,其他数都出现了M次,M > 1, K < M找到,出现了K次的数,要求,额外空间复杂度O(1),时间复杂度O(N)

    • 自定义一个数组记录数组中每一个数每一位出现的次数

    • 遍历数组,每个数的每一位是否存在,如果存在就加在对应的位数上

    • 定义返回的结果值ans,遍历自定义的数组,拿到数组中对应的值与m进行模运算,如果等于0就将对应位的值赋值到ans中

      public static int onlyTimes(int[] arr, int k, int m) {
          // 用数组来表示每位的个数
          int[] t = new int[32];
          // t[0] 0位置的1出现了几个
          // t[i] i位置的1出现了几个
          for (int num : arr) {
              for (int i = 0; i < 32; i++) {
                  // 不为0,则num在i位上为1
                  //				if (((num >> i) & i) != 0) {
                  //					t[i]++;
                  //				}
                  // 优化:如果为0,加了个寂寞
                  t[i] += (num >> i) & 1;
              }
          }
          int ans = 0;
          // 看每一位上的数与m取模是否为0,如果不为0则说明有k
          // 分析:如果其他数出现m次,那么应该为0,不为0则说明有出现K次的数
          for (int i = 0; i < 32; i++) {
              if (t[i] % m != 0) { // 第i位上有1
                  ans |= (1 << i);// 把1设置上去
              }
          }
          return ans;
      }
      
  • KM题扩展:如果另一个数不为K次,则结果返回-1

    public static int onlyTimes2(int[] arr, int k, int m) {
        // 用数组来表示每位的个数
        int[] t = new int[32];
        // t[0] 0位置的1出现了几个
        // t[i] i位置的1出现了几个
        for (int num : arr) {
            for (int i = 0; i < 32; i++) {
                t[i] += (num >> i) & 1;
            }
        }
        int ans = 0;
        // 看每一位上的数与m取模是否为0,如果不为0则说明有k
        // 分析:如果其他数出现m次,那么应该为0,不为0则说明有出现K次的数
        for (int i = 0; i < 32; i++) {
            // 模完等于0,说明这一位不含有1
            if (t[i] % m == 0) {
                continue;
            }
            // 出现k次才统计进去
            if (t[i] % m == k) {
                ans |= (1 << i);
            } else {// 出现的不是k次
                return -1;
            }
        }
        if (ans == 0) {
            int count = 0;
            for (int num : arr) {
                if (num == 0) {
                    count++;
                }
            }
            if (count != k) {
                return -1;
            }
        }
        return ans;
    }
    
  • 对数器(以最后一道题为例)

//对数器
public static void main(String[] args) {
    int testTimes = 10000;
    int maxValue = 100;
    int kinds = 10;
    int k = 8;
    int m = 14;
    System.out.println("test begin");
    for (int i = 0; i < testTimes; i++) {
        int[] arr = generateArr(kinds, k, m, maxValue);
        int orgin = onlyTimes(arr, k, m);
        int test = test(arr, k);
        if (orgin != test) {
            System.out.println("fail");
        }
    }
    System.out.println("test end");
}

public static int test(int[] arr, int k) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int num : arr) {
        if (map.containsKey(num)) {
            map.put(num, map.get(num) + 1);
        } else {
            map.put(num, 1);
        }
    }
    for (Integer num : map.keySet()) {
        if (map.get(num) == k) {
            return num;
        }
    }
    return -1;
}

// 一个数组中有一种数出现K次,其他数都出现了M次, M > 1, K < M 找到,出现了K次的数, 要求,额外空间复杂度O(1),时间复杂度O(N)
private static int[] generateArr(int kinds, int k, int m, int maxValue) {
    int len = (k * 1) + ((kinds - 1) * m);
    int[] arr = new int[len];

    int index = 0;
    int kTimesValue = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * (maxValue + 1));
    for (; index < k; index++) {
        arr[index] = kTimesValue;
    }

    kinds--;

    HashSet<Integer> set = new HashSet<>();
    set.add(kTimesValue);

    while (kinds != 0) {
        int curValue = 0;
        do {
            curValue = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * (maxValue + 1));
        } while (set.contains(curValue));
        set.add(curValue);
        kinds--;

        for (int i = 0; i < m; i++) {
            arr[index++] = curValue;
        }
    }
    for (int i = 0; i < arr.length; i++) {
        int j = (int) Math.random() * arr.length;
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    return arr;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值