剑指offer第二版——面试题56(java)

面试题:数组中数字出现的次数

题目一:数组中只出现一次的两个数字

    一个整型数组中,除了两个数字之外,其他数字都出现了两次。时间复杂度O(n),空间复杂度O(1)

题目二:数组中唯一只出现一次的数字

    在一个数组中,除一个数字只出现一次之外,其他数字都出现了三次

思路:

题目一:

参考的其他po:

https://www.cnblogs.com/shiganquan/p/9350685.html

https://blog.csdn.net/koala_tree/article/details/79617607

由于要求时间复杂度O(n),所以不能用排序的方法(最低nlogn);空间复杂度为1,所以不能用哈希表(不能开辟新空间)

先将题目一分为两个部分:1)将两个只出现1次的数字分到两个子数组中;2)在只含1个只出现1次的数字,其他数字都出现两次的数组中,找出那个只出现1次的数字

先解决2):

使用异或:任何一个数字异或它自己都等于0

因此,如果一个数组中只有1个只出现1次的数字,其他数字都出现了2次,那么依次异或(data[1]异或data[2],再异或data[3],再异或……),出现过两次的数字都被异或掉了,只会剩下那个只出现过1次的数字

异或是在2进制上的,将10进制的数表达为2进制,如三个数1,2,2,3,3,则1异或2后得11,11异或2得01,01异或3为10,10异或3为01,换为10进制为1

再解决1):

在原始数组中,有两个不同的只出现过1次的数字,在依次异或之后,剩下的结果为num1和num2异或的结果,其结果表示的是num1和num2在2进制表达中,不相同的位置,我们取它们第一个不同的位置index对num1和num2进行区分,并以此来对原始数组进行子数组的划分

为什么可以以2进制在index位置是否为1或是否为0为划分:

对num1和num2来说,在index位置是不同的,因此肯定是一个为1,一个为0,可划分开

对出现了两次的其他数来说,相同的数在任意位置的值都是相同的,所以以index位置的值来划分,可将相同的数都划分到同一个子数组中,不会出现相同的两个数分别被分到两个子数组中的情况

方法:

基于以上思路,代码步骤为:

1)先对原始数组依次异或,得到异或结果

2)找到第一个1的位置(从右到左),将其定为index,用于子数组和num1、num2的划分

3)对原始数组依次判断index位置是否为1/0,并分别异或自己子数组的数

4)得到两个子数组的异或结果,就分别是num1和num2

 

题目二:

如果一个数字出现三次,那么它的二进制表示的每一位也出现三次,如果把所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位的和都能被3整除。

∴ 如果把数组中所有数字的二进制表示的每一位都加起来,如果某一位的和能被3整除,那么那个只出现一次的数字的二进制表示中对应的那一位是0,否则就是1

使用长度为32位的辅助数组存储二进制表达的每一位的和,是因为java的int数据类型是32位

 

【代码】

题目一:

public class Q56_1 {
	public static void main(String[] args) {
		int[] list = new int[] {2,4,3,6,3,2,5,5};
		int[] nums = getOnce(list);
		if(nums!=null) {
			System.out.printf("%d %d",nums[0],nums[1]);
		}
	}
	
	public static int[] getOnce(int[] list) {
		if(list.length<2) {
			System.out.println("less than two");
			return null;
		}
		
		// 1) 先对原始数组依次异或,得到异或结果
		int result = 0;
		for(int i=0;i<list.length;i++) {
			result = result^list[i];
		}
		
		// 2)找到第一个1的位置(从右到左),将其定为index,用于子数组和num1、num2的划分
		int index = getIndex(result);
		if(index==-1) {
			return null;
		}
		
		// 3)对原始数组依次判断index是否为1/0,并分别异或自己子数组的数
		int num1=0,num2=0;
		for(int i=0;i<list.length;i++) {
			if(is1(list[i],index)==1) {
				num1 = num1^list[i];
			}else {
				num2 = num2^list[i];
			}
		}
		
		// 4)得到两个子数组的异或结果,就分别是num1和num2
		return new int[] {num1,num2};
	}
	
	
	// 2)找到第一个1的位置(从右到左),将其定为index,用于子数组和num1、num2的划分
	public static int getIndex(int result) {
		if(result==0) {
			System.out.println("there are all double numbers!");
			return -1;
		}
		
		int index = 0;
		while((result&1)==0) {
			result = result>>1;
			index++;
		}
		return index;
	}
	
	// 判断数的index位置是否为1/0
	public static int is1(int x,int index) {
		x = x>>index;
		return (x&1);
	}
}

题目二:

public class Q56_2 {
	public static void main(String[] args) {
		int[] list = new int[] {2,6,5,4,2,6,4,2,4,6};
		int x = getOnce(list);
		System.out.println(x);
	}
	
	public static int getOnce(int[] list) {
		// 数组长度为1
		if(list.length==1) {
			return list[0];
		}
		
		// 不满足条件的数组长度
		if(list.length<4) {
			System.out.println("wrong input!");
			return -1;
		}
		
		// 按位加每一个数的每一位
		int[] bitSum = new int[32];
		for(int i=0;i<list.length;i++) {
			int bitloc = 1;
			for(int j=31;j>=0;j--) {
				int bit = list[i]&bitloc;
				if(bit!=0) {
					bitSum[j]++;
				}
				bitloc = bitloc<<1;
			}
		}
		
		// 对每一位整除3得结果——所求数的每一位是1还是0
		int result = 0;
		for(int i=0;i<32;i++) {
			result = result<<1;
			result+=bitSum[i]%3;
		}
		return result;
	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值