好玩的位运算1:找数组中找成对和不成对

本文探讨了在解决数组中寻找唯一出现一次的数字的问题时,如何利用位运算实现高效算法。对比了使用哈希表的方法,强调位运算是更优的选择,特别是在空间复杂度和时间复杂度上。通过异或操作,可以在不需要额外存储空间的情况下,直接找出数组中只出现一次的数字。此外,还介绍了如何在1到1000的数字中找到唯一重复的数字,同样利用位运算技巧,避免了辅助存储空间。
摘要由CSDN通过智能技术生成

如果会位操作的话,很多题目特别简单,如果不知道想用其他结构来做的话,很多会非常难,而且麻烦。我们看一个经典的题目:一个数组里除了某一个数字之外,其他的数字都出现了两次,请写程序找到这个只出现一次的数字。

这个题如果采用Hash等方式虽然能做,就是先遍历一遍计数,然后再遍历一次找满足要求的结果,这样能做但是效果非常不好。如果采用位运算就非常简单,我们知道位运算具体来说包括与或非、异或等几种,异或是一种比较特殊的操作,特点是满足以下性质:

A^A=0
A^0=A
A^A ^B=B
交换律:A^B ^C=A ^C ^B
结合律:A^B ^C=A ^(B ^C)

一个数和它自己异或就同归于尽了,这个性质在很多算法题中有妙用,包括本题。而且这个特征即使两个相同的数字不在靠近的位置仍然有效,也就是如果A^B ^A=B仍然成立。所以这个问题的实现就很容易了:

public static int findNumAppearOnce(int[] arr) {
        int result = 0;
        for (int i = 0; i < arr.length; i++) {
            result ^= arr[i];
        }
        return result;
}

这个题用Hash和计数方式也能做,思路也不复杂,这个可以可以使用一个Map,Map对应的键值key就是数组中的元素,value就是这个元素出现的次数。这样我 通过一次遍历数组中的元素,如果元素出现在map中,则将其对应的value加1,否则将元素添加到map中,这样遍历一遍数组,我们就可以得到数组中每个元素对应出现的次数,然后再通过遍历一遍map,返回value为1对应的key就是我们需要求得元素。实现如下:

 public static void findNumsAppearOnce1(int[] arr) {
        //Map对应的键值key就是数组中的元素,value就是这个元素出现的次数
        Map<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
        for (int i : arr) {
            if (hashMap.containsKey(i)) {
                hashMap.put(i, hashMap.get(i) + 1); //如果存在,value值加1
            } else {
                hashMap.put(i, 1); //如果不存在,存入hashMap,value值等于1
            }
        }

        //遍历一遍map,返回value为1对应的key就是我们需要求得元素
        Set<Integer> set = hashMap.keySet();
        for (int i : set) {
            if (hashMap.get(i) == 1) {
                System.out.print(i+" ");
            }
        }
    }

很明显这么做的代价很高的。时间复杂度方面:因为首先需要遍历一遍数组,时间开销为O(n),构建完map后需要遍历一遍map找到value为1的元素,而map的个数为n/2,时间开销为O(n/2),所以总的时间开销为O(n)。 空间复杂度方面:因为需要建立一个map,而且最后map的大小为n/2,所以空间复杂度为O(n)。

而如果使用位操作,简单直接,再看一个题目:

1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其他均只出现一次。每个数组元素只能访问一次,设计一个算法,将他找出来;不用辅助存储空间,能否设计一个算法实现?

1000个数中只有一个会冲突,很显然这不是面试官想要的解答

我们要想办法将不重复的那999个干掉,剩下的自然就是重复的了,该怎么做呢?题目要求里说1-1000这1000个数放在含有1001个元素的数组,这个条件非常重要,也就是说数组里一定包含1到1000的,只不过不一定有序,那么我们可以再拿一个大小为1000的newArray[]与原始数组initArray[]进行异或,那么自然重复的999个就消失了。而有重复的那个元素,例如是9,在newArray[]和initArray[]中一次出现了三次,而其中两个抵消了,最后剩下的就是剩余的了,因此这就是我们要找的那个。

import java.util.*;
public class UniqueNumber {
public static void main(String[] args) {
	int N=1001;
	int []arr =new int[N];
//	前N-1位为从1到N-1
	for(int i=0;i<N-1;i++)
	{
		arr[i]=i+1;
	}
//	第N位为随机产生的一个1到N-1的数字
	arr[N-1]=new Random().nextInt(N-1)+1;
	for(int i=0;i<N;i++)
	{
		System.out.print(" "+arr[i]);
	}

//	只要将两个数组的元素全部异或一下就行,顺序无所谓,所以这里先异或一下
	int x=0;
	 for(int i=1;i<N;i++)
	 {
		x=x^i;
	}
	for(int i=0;i<arr.length;i++)
	{
		x=x^arr[i];
	}
	System.out.println("唯一成对的那个数是:"+x);
 }
}

这个方法还可以使用辅助空间等,比较难理解,而且容易将自己绕晕,位运算最合适。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值