02、找出出现奇数次的两个数(JAVA)--异或

题目描述

image.png

例如:
输入: nums = [4,1,4,6]
输出: [1,6] 或 [6,1]

力扣传送门(点击)


要理解这道题的思路,首先要先对异或(符号为^)有一个基本认识,不明白的兄弟可以看看上一篇文章,里面有异或以及相关用法的讲解:

链接奉上(点击)


思路解析

这道题看起来跟上一道题大同小异(找出出现奇数次的一个数),其实还是要动点脑子,但主要还是要运用异或的思想来解题。如果要找的只有一个数字,那我们直接异或数组内的所有元素即可,但题目的要求是找出两个出现奇数次的数,这要怎么搞呢?

我们可以将数组分成两个组。

这时可能有兄弟会问,为什么要分组?分组要怎么分?换句话说,分组的依据到底是什么?分组有什么用等等问题,不急,我们慢慢来。


1、分组的目的

简单来讲,目的就是为了将出现奇数次的那两个数分开。我们假设要找的两个数为num1num2,两个组分别为arr1arr2,那么分完以后,num1就应该在其中一个组,而num2在另一个组。而因为其他数都出现了偶数次,那么arr1arr2中的数,除了num1num2以外,其他的数都应该出现了偶数次重点来了

  • 我们可以将这两个组中的任何一个组进行异或,最后的异或出来的结果,一定是num1和num2中的一个(假设是num1)。

  • 那么要找另一个出现奇数次的数(假设是num2),我们只需直接将原数组(就是分组前的大数组)中的所有元素进行异或,得出来的结果肯定是num1^num2后的结果(假设是eof,eof=num1 ^ num2),再用eof异或一次num1,得出来的值毫无疑问就是num2了。

至此,num1和num2就都找出来了。接下来让我们谈谈分组的依据,这个组到底要怎么分?

2、分组的依据

既然题目要我们找出的是两个数,那么这两个数就绝不可能相等,因此当我们把原数组内的所有元素异或完以后(还是假设结果为eof),eof的值就不可能是0,因为两个相同的数异或的结果就是0。这时我们换个角度想想,为什么两个相同的数异或的结果是0?因为他们的二进制上的每一位都相同,既然eof不等于0,那就说明num1和num2的二进制上肯定有至少1位不同(0^1=1) ,所以,我们就可以按照这个特征来将num1和num2分开。

有兄弟可能又会问,思路我是知道了,但是不会敲啊!比如怎么找二进制上不同的那一位呢?别急,看下面的代码。


代码解析

现在让我们来回答上面的问题,怎么找二进制上不同的那一位?一行代码足矣:

//提取eof二进制上最右边的1
int rightOne=eof&(~eof+1);
//eof=num1^num2
//~eof:对eof进行取反操作(二进制上所有的1变成0,所有的0变成1)
//eof&(~eof+1):eof与上eof取反后加1的值
//例如:1001101,
//取反后就是0110010,加1就是0110011,最后与上1001101
//结果就是0000001,就是它二进制上最右边的1

因此,num1和num2的二进制上所有不同位上的其中一位,就被我们找出来了。接下来,我们需要做的就是再遍历一遍原数组,如果有元素符合num1的特征(假设num1和num2不同的那一位的二进制是1,num2是0),则跟num1分到同一个组,不符合的直接不管就行了。

//arr表示原数组
for(int a : arr) {
			if((a&rightOne)!=0) {
				num1=num1^a;
                      //若满足条件,则和num1分到同一组
                      //例如:
                      //假设rightOne=000010
                      //如果arr内有元素的二进制在第二位上也是1,比如0101110
                      //则0101110就跟num1一个组
			}
		}

最后,分好组后,按照上面所讲的方法,即可找出num1和num2的值。下面是完整的代码:

完整代码

package 苟熊岭熊哒;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
	public static void main(String[] args) throws IOException{
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		int n=Integer.parseInt(br.readLine());//输入数组的长度
		String[] line=br.readLine().split(" ");//输入数组的内容
		int[] arr=new int[n];
		for (int i = 0; i < arr.length; i++) {
			arr[i]=Integer.parseInt(line[i]);
		}
           //上面的代码都是输入的过程,刷题时可以忽略
		
           //找出出现奇数次的两个数
		FindTwoOddTimes(arr);
	}
	
	public static void FindTwoOddTimes(int[] arr) {
		int eof=0;
		for (int i = 0; i < arr.length; i++) {
			eof=eof^arr[i];
		}
		//eof=a^b
		//因为a和b不相等,所以eof1必不等于0,因此eof的二进制中一定有一位上是1
		//也就是a和b中在那一位上,肯定有一个是0,另一个是1
		//找出为1的位
		
		int rightOne=eof&(~eof+1);//提取出最右边的1,非常牛逼的一句代码
		//eof与上(eof的取反加1)
		
		//将arr分为两组数,一组为二进制最右边为1的数,一组为二进制最右边不为1的数
		//假设a在第一组,b在第二组
		int num1=0;//存二进制最右边为1的数
		for(int a : arr) {
			if((a&rightOne)!=0) {
				num1=num1^a;
			}
		}//num1最后的结果就是a
		
		int num2=eof^num1;//num2最后的结果就是b
		
        //判断大小
		int max=Math.max(num1, num2);
		int min= Math.min(num1, num2);
		
		System.out.println(min+" "+max);
	}
}

输出结果:

image.png


力扣题解

代码:

class Solution {
    public int[] singleNumbers(int[] nums) {
    int eof = 0;
		for (int num : nums) {
			eof ^= num;
		}
		int tmp = eof & ((~eof) + 1);
		int num1 = 0;
		// 分组
		for (int num : nums) {
			if ((num & tmp) != 0) {
				num1 = num1 ^ num;
			}
		}
		int num2 = eof ^ num1;
		int[] newArray = { num1, num2 };
		return newArray;
    }
}

运行效果:
在这里插入图片描述


这道题的介绍到这里就结束了,如果你觉得本篇文章对你多少有些帮助,可以点个赞或者收藏一波支持一下哦,欢迎各位大佬批评指正,咱们下次再见!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值