准备蓝桥杯之路(二) ------ 位运算的奇淫技艺

准备蓝桥杯之路(二) ------ 位运算的奇淫技艺

前言

  本人之前接触过最多的语言是python和java,平时用的较多的还是python,不过大赛有规定,研究生只能报java组,所以我计划之后的相关学习就基于java进行,但是算法最重要的还是思想,编程语言只是一个实现思想的工具而已。

  因为这差不多算是该系列的第一篇博客,所以讲一下这个系列博客的主体框架吧,我会根据每天学习的内容,总结一下几道重要的习题,讲解一下每道题的解题思路并附上java的代码实现,同时也会详细介绍每道题所涉及的重要知识点、java编程中一些需要注意的点以及自己的一些理解等等,最后就可能会有一些总结之类的东西!!!

我是跟着哔哩哔哩的一个课程进行学习的,毕竟不是原创,所以下面我附上视频链接。
视频链接: 蓝桥杯零基础入门到比赛(二)——算法很美

习题

1. 找出唯一成对的数

题意描述: 1~1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助空间。

解题关键: 该题的关键点就是: a ^ a = 0,a ^ 0 = a这两个公式,即通过异或操作消除掉一些数。通过题意可知,这1001个数分别是:1~1000这1000个数和剩下的一个数(我们要找到的数),所以我们就可以通过异或操作将原数组中的1~1000这1000个数消掉,这样就能得到我们想要的那个数。

当然这道题肯定还有其他方法可以解决,不过要注意题中给的限定条件,不用辅助空间。为了增加更多的解题思路,我把使用辅助空间的代码实现也贴出来,供大家参考。

假设arr为题中所说的数组,且arr[1001]中存的数就是那个成对的数。则上述解题关键用公式表述出来就是:
注:其实刚开始第一步的转换我有点迷,基本功实在是太差了,最后查了一下,由于异或操作是满足交换律和结合律的,所以,结合交换之后,再根据上面的公式就消掉了。
arr[1] ^ arr[2] ^ arr[3] ^ … ^ arr[1001] ^ 1 ^ 2 ^ 3 ^ … ^1000 = arr[1001] ^ 0 = arr[1001]

代码实现

import java.util.Random;

public class question_1
{
	public static void main(String[] args)
	{
		//生成题中的数组
		int N = 1001;
		int swap = 0;
		int[] arr = new int[N];
		for(int i = 0; i < arr.length-1; i++){
			arr[i] = i + 1;
		}
		//生成1-10的随机整数
		arr[arr.length-1] = new Random().nextInt(N-1) + 1;
		//随机下标 new Random().nextInt(N) --> 生成一个0-N(不包括N)的随机整数
		int index = new Random().nextInt(N);
		//随机打乱一下顺序
		swap = arr[arr.length-1];
		arr[arr.length-1] = arr[index];
		arr[index] = swap;
		//打印数组
		for(int i = 0; i < arr.length; i++){
			System.out.print(arr[i] + " ");
		}

		//题解
		/**
		* 解题关键: 异或操作满足交换律 a^b^c = a^(b^c)
		*           a^a = 0 
		*           a^0 = a
		*/
		int answer = 0;
		for(int i = 1; i <= N-1; i++){
			answer = answer ^ i;
		}
		for(int i = 0; i < N; i++){
			answer = answer ^ arr[i];
		}
		System.out.println("\n这1001个元素中重复的元素是:" + answer);
		
		System.out.println("========================================");
		System.out.println("暴力破解法(使用辅助空间)---不符合题意");
		int[] helper = new int[N];
		for(int i = 0; i < N; i++){
			helper[arr[i]]++;
		}
		for(int i = 0; i < N; i++){
			if(helper[i] == 2){
				System.out.println("\n这1001个元素中重复的元素是:" + i);
			}
		}		
	}
}

2. 找出落单的那个数

题意描述: 一个数组中除了某一个数字之外,其他的数字都出现了两次。请写出程序找出这个只出现一次的数字。

解题关键: 这道题和第一题的思路基本上是一样的,而且是比第一题还要简单的,所以思路参考第一题即可。这里直接给代码实现了。

代码实现 `

public class question_2
{
	public static void main(String[] args)
	{
		int N = 13;
		int[] arr = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7};

		//题解
		/**
		* 解题关键: 异或操作满足交换律 a^b^c = a^(b^c)
		*           a^a = 0 
		*           a^0 = a
		*/
		int answer = 0;
		for(int i = 0; i < N; i++){
			answer = answer ^ arr[i];
		}
		System.out.println("\n这13个元素中落单的元素是:" + answer);
	}
}

3. 二进制中1的个数

题意描述: 请实现一个函数,输入一个整数,输出该数二进制表示中的1的个数。例如: 输入9的二进制表示为1001,有2位是1,输出为2。

解题关键: 这道题有多种解题方法,多掌握一个方法就在比赛的时候多点保障,多学无罪。这里主要讲两种:第一种是用“与”操作判断某位是否为1,因为 1 & 1 = 1,1 & 0 =0,即如果"与"操作之后的结果为1,则证明该位是1,否则该位为0;第二种方法有点难理解,是通过(number - 1) & number这个公式消掉number中最末尾的1,通过记录需要消1的次数来统计1的个数。

再简短的介绍一下我对(number - 1) & number这个公式的理解(这个公式我认为还是蛮有用的):
首先将number转化为二进制的形式,如7的二进制形式为 0111,然后带入上述公式,(0111 - 0001) & 0111 = 0110,这样我们可以看到最末尾的1就被消掉了,通过三次这样的操作后,所有的1都会被消掉,也就是题解关键中所说的“通过记录需要消1的次数来统计1的个数”。

代码实现 `

第一种方法有两种实现形式,大家可以看代码理解下:

import java.util.Scanner;

public class question_3
{
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		System.out.print("请输入一个整数:");
		int number = in.nextInt();

		System.out.println("========================");
		System.out.println("第一种解法");
		//题解
		/**
		* 解题关键:  1 & 1001 = 1
		*           1 & 1000 = 0
		*          10 & 1001 = 0
		*          10 * 1011 = 10
		*          int型总共占4个字节共32位
		*/
		int count = 0;
		//循环32次是因为java中int类型占4字节共32位
		for(int i = 0; i < 32; i++){
			if((number & (1 << i)) == (1 << i)){
				count ++;
			} 
		}
		System.out.println(number + "的二进制表示中1的个数为" + count);

		System.out.println("========================");
		System.out.println("第二种解法");
		//题解
		/**
		* 解题关键:  和第一种解法有异曲同工之妙
		*/
		count = 0;
		for(int i = 0; i < 32; i++){
			if(((number >> i) & 1) == 1){
				count ++;
			} 
		}
		System.out.println(number + "的二进制表示中1的个数为" + count);
	}
}

第二种方法:

import java.util.Scanner;

public class question_3
{
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		System.out.print("请输入一个整数:");
		int number = in.nextInt();
		
		System.out.println("========================");
		System.out.println("第三种解法");
		//题解
		/**
		* 解题关键: (number - 1) & number 消掉最末尾的1
		*/
		int count = 0;
		while(number != 0){
			number = (number - 1) & number;
			count++;
		}
		System.out.println(number + "的二进制表示中1的个数为" + count);
	}
}

4. 是不是2的整数次方

题意描述: 用一条语句判断一个整数是不是2的整数次方。

解题关键: 这一题就要用到题3中的那个重要公式了,我们首先分析一下2的整数次方的数具有什么性质,从整数二进制的表示上分析,2的整数次方的数的二进制表示中只可能有一个1(哈哈哈,这句话太绕了),所以我们可以通过题3的公式消掉这个1,看消掉之后的结果是否是0就可以了。
ps:这道题真的很神奇,用一条语句判断,献上我的膝盖,我果然现在还处于算法幼儿园的水平!!!

代码实现 `

import java.util.Scanner;

public class question_4
{
	public static void main(String[] args)
	{
		//题解
		/**
		* 解题关键: (number - 1) & number 消掉最末尾的1
		*          2的整数次方的整数 其2进制表示中只有一个1,消掉之后就变成0了
		*/
		Scanner in = new Scanner(System.in);
		System.out.print("请输入一个整数:");
		int number = in.nextInt();

		if(((number - 1) & number) == 0){
			System.out.println(number + "是2的整数次方");
		}else{
			System.out.println(number + "不是2的整数次方");
		}
	}
}

5. 交换奇偶位

题意描述: 例如 输入7的二进制表示位 0111 交换奇偶位后 为 1011 转为10进制就是11,即输出为11。

解题关键: 这道题就是"与“、“或”、“异或”运算以及位移动的综合运用,通过这两个公式1 & X = X,0 | X = X可以保留自己想要的值。对于这道题来说,首先通过number和10101010101010101010101010101010(java中int类型占4字节共32位)进行“与”操作保留偶数位的值,然后再通过number和01010101010101010101010101010101进行“与”操作保留奇数位的值,最后将奇数位的值左移一位,偶数位右移一位后进行"或"操作后,就能得到交换后的值。

注:这个地方自己可以通过找一个实例然后在纸上写一遍就能理清思路了,现在我还不知道如何插入示例图比较合适,如果以后想到了会更新的,更便于理解,见谅!!!

代码实现 `

import java.util.Scanner;

public class question_5
{
	public static void main(String[] args)
	{
		Scanner in = new Scanner(System.in);
		System.out.print("请输入一个整数:");
		int number = in.nextInt();

		//题解
		//保留奇数位
		//0x55555555 就是 01010101010101010101010101010101 的十六进制表示,下同。
		int odd = number & 0x55555555;
		//保留偶数位
		int even = number & 0xaaaaaaaa;
		//奇数位左移一位 和 偶数位右移一位 进行"或"操作或者"异或"操作
		int answer = (odd << 1) | (even >> 1);
		System.out.println("奇偶位交换之后的数为:" + answer);
	}
}

总结

知识感悟:通过今天的学习来看,我从来都没有踏入过编程的门,之前真的是太自大了,总觉得自己已经懂了很多,结果很多位操作的事情都搞不懂。所以还是那句slogan送给自己:好好学习,争取早日毕业。

精神感悟:事情(写博客)真的是看别人做觉得很简单,自己下手就知道有多难了。不过还是挺开心的,能把自己学的知识总结记录下来,希望自己以后能够加油坚持下去,持续更新吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值