算法周练二

72 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

如何不用额外变量交换两个数

这个题目是要用异或来解决的。

int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;

这个交换需要注意的是,如果是同一个地址上的元素的话,会被清零。

这边运用到的知识其实就是,相同的两个数异或会变成0,一个数和0异或还是原来的值。

一个数组中有一种数出现了奇数次,其他都出现了偶数次,打印那个奇数次的数

这道题也是利用异或来解决。

或许可以想到哈希表来解决,但是需要申请内存。

我们可以想想异或的知识点,相同的两个数异或会变成0,那么偶数次一起异或是不是就变成0了,然后奇数次的总会留下一个,那么这一个不就是答案了嘛。

int ret = 0;
for(int i = 0;i<arr.length;i++){
    ret ^= arr[i];
}
System.out.println(ret);

我们创建一个变量,然后让这个变量循环和数组里面的数异或,最后得出的就是答案

如果有两个数是奇数次

这道题目是上面那个题目的进化版,那我们怎么做呢?

当然还是像上面那道题目一样,先定义一个变量,然后和数组里面的所有数异或。最后得到的数一定是这两个答案的异或,现在的目的就不一样了,我们现在要区分这两个数。

那我们怎么区分最后的答案呢?

我们想想异或的性质。相同为0,相异为1,那最后异或出来的数,有1的地方是不是他们不同的地方。

那就是说,有1的地方,就是一个数是0,一个数是1,那我们是不是把他们两个区分出来了,那我们就可以拿这个数的最右边的那个1来区分。

int ret = 0;
for(int i = 0;i<arr.length;i++){
    ret ^= arr[i];
}
int rightOne = ret & (-ret);    //这个就是提取int数最右边的那个1.
int ans1 = 0;
int ans2 = 0;
for(int i = 0;i<arr.length;i++){
    if((arr[i] & rightOne) != 0){   //这个if就是为了区分两个不同的数的。
        ans1 ^= arr[i];
    }else{
        ans2 ^= arr[i];
    }
}
System.out.println(ans1);
System.out.println(ans2);

提取int数最右边的那个1

这个题目很简单,只要一个数&自己的相反数

    假设 a = 12;
    二进制:1100
    -a = -12
    二进制 0100
    1100 &  0100 = 0100
    

一个数组中有一个数出现了K次,其他的数就出现了M次,求出现K次的数

这边还有一定的要求,M > 1 , K < M

这道题目还是很巧妙的,巧妙的地方在于,用数组来记录二进制的形式。我们可以申请一个长度为32的数组,然后里面的每一位都记录着这个数组里面所有数字的二进制1在某一位的个数。

然后我们对数组进行检索,如果有个位上的1不是M倍,那么就说明K次出现的数这个位置上是1.

int[] help = new int[32];
for (int num : arr) {
    for (int i = 0; i < 32; i++) {
        help[i] += (num >> i) & 1;
    }
}
int ans = 0;
for (int i = 0; i < 32; i++) {
    help[i] %= m;
    if (help[i] != 0) {
        ans |= (1 << i);
    }
}
return ans;

计算一个数有多少个1

这次我们要用到与这个符号。

我们知道1 & 0 = 0;

1 - 1 = 0;

10 - 1 = 01;

我们通过上面的二进制减法可以看到,减去1之后,我们最右边的那个1会变成0;

那我们循环减去然后和自己&,最后是不是会变成0;

那么代码就出来了

int num = 0;
int a = 15;
while(a != 0){
    a = a & (a - 1);    //这个就是代码的核心。
    num++;
}
System.out.println(num);