蓝桥杯《算法很美》第1章:位运算

224 阅读4分钟

视频链接:《算法很美》

1.位运算

位运算符

  • 在处理整形数值时,可以直接对组成整形数值的各个二进制位进行操作
  • &(与)、| (或)、^(异或)、~ (非/取反)
  • >><<运算符将二进制位进行右移或者左移操作
  • >>>运算符将用0填充高位; >>运算符用符号位填充高位,没有<<<运算符
  • 对于int型,1<<351<<3是相同的,而左边的操作数是long型时需对右侧操作数模64
  • 与:都为1结果为1,或:有一个为1结果为1,异或:二者不同时结果为1
ab~aa&ba|ba^b
110110
011011
001000

位运算符的一些常见用法

  • 1.判断奇偶数

    • x&1=1为奇数 =0为偶数 原因:奇数最后一位是1,&1后为1,偶数最后一位为0,&1后为0
  • 2.判断二进制某个位上是1还是0 (两种解决方案)

    • eg:86的二进制第5位是1还是0?
    • 1先左移4位和86与运算,结果再右移,和为1则为1,为0则为0
    • 86右移4位,然后结果和1与运算,结果1则为,为0则为0
      86>>4&1
  • 3.交换两个整数变量的值

int a=2,b=1;
a=a^b;
b=a^b;
a=a^b;
System.out.println("a=="+a+",b=="+b);
  • 4.不用判断语句,求整数的绝对值
int a=-88;
System.out.println((a^a>>31)+(a>>>31));
// 结果:88

异或

在这里插入图片描述

第1题:找出唯一称对的数

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

解题思路

在这里插入图片描述

答案如下

public class Test01 {
	public static void main(String[] args) {
		// 这里为了演示方便,数组长度为11
		int N = 11;
		int[] arr = new int[N];
		for (int i = 0; i < arr.length - 1; i++) {
			arr[i] = i + 1;
		}
		arr[N - 1] = new Random().nextInt(N - 1) + 1;
		int index = new Random().nextInt(N);
		int t;
		t = arr[N - 1];
		arr[N - 1] = arr[index];
		arr[index] = t;
		// 打印这个数组
		System.out.println(Arrays.toString(arr));

		// 1.位运算方法:异或
		int x = 0;
		// 1^2...^x^x..^10
		for (int i = 0; i < N; i++) {
			x = x ^ arr[i];
		}
		// (1^2...^x^x..^10)^(1^2...^x...^10)
		for (int i = 1; i <= N - 1; i++) {
			x = (x ^ i);
		}
		// x^x^x = x =>得到结果x
		System.out.println(x);

		// 2.辅助空间方法
		int[] h = new int[N];
		for (int i = 0; i < N; i++) {
			h[arr[i]]++;
		}
		for (int i = 0; i < N; i++) {
			if (h[i] == 2) {
				System.out.println(i);
			}
		}

		// 3.哈希表法
	}
}

第2题:找出落单的那个数

一个数组里除了某一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。

解题思路

这道题思路简单很多,只需要用到A ^ A =0A ^ 0 = A即可!

答案如下

public class Test02 {
	public static void main(String[] args) {
		// 假设数组数据如下:求数组中的唯一不重复数据(4)
		int[] arr = new int[] { 1, 1, 3, 3, 4, 5, 5, 7, 7, 9, 9 };

		int x = 0;
		for (int i = 0; i < arr.length; i++) {
			x = x ^ arr[i];
		}
		System.out.println("数组arr中的唯一不重复元素是:" + x);// 4
	}
}

第3题:二进制中1的个数

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

解题思路

在这里插入图片描述

答案如下

public class Test03 {
	public static void main(String[] args) {
		
		System.out.println("请输入一个数:");
		Scanner scanner = new Scanner(System.in);
		int N = scanner.nextInt();
		
		String binN = Integer.toString(N,2);
		System.out.println("您输入的数字:"+N+" 对应的二进制为:"+binN);
		
		// 二进制中1的个数
		int count = 0;
		for (int i = 0; i < binN.length(); i++) {
			if ((N&1<<i)== 1<<i) {
				count++;
			}
		}
		System.out.println("该二进制中1的个数为:"+count);
	}
}

结果如图:

在这里插入图片描述

第4题:判断一个数是否是2的n次方

用一条语句判断一个整数是不是2的整数次方。

解题思路

性质:一个数如果是2的整数次方,则该数的二进制中只有一个1

  • 2的0次方:1 => 1
  • 2的1次方:2 => 10
  • 2的2次方:4 => 100
  • 2的3次方:8 => 1000

所以我们只需要判断某个数的二进制是否满足上述性质,即可判断该数是不是2的整数次方。

答案如下

public class Test04 {
	public static void main(String[] args) {
		System.out.println("请输入一个数:");
		Scanner scanner = new Scanner(System.in);
		int N = scanner.nextInt();

		String binN = Integer.toString(N, 2);
		System.out.println("您输入的数字:" + N + " 对应的二进制为:" + binN);

		// 二进制中1的个数
		int count = 0;
		for (int i = 0; i < binN.length(); i++) {
			if ((N & 1 << i) == 1 << i) {
				count++;
			}
		}
		System.out.println("该二进制中1的个数为:" + count);
		System.out.println("该数是否是2的整数次方:"+ (count==1));
	}
}

结果如图:

在这里插入图片描述

第5题:将整数的二进制奇偶位互换

将整数的二进制奇偶位互换,然后得到交换后的整数值

解题思路

在这里插入图片描述

答案如下

public class Test05 {
	public static void main(String[] args) {
		// 如果不采用位运算,可以采用整数转二进制字符串,然后对其进行分割成char数组,然后遍历数组并交换奇偶位
		// 这里我们采用位运算的方式:
		int N = 99;
		System.out.println("99的二进制为:" + Integer.toString(N, 2));

		int result = swap(N);
		System.out.println("二进制奇偶位交换后的结果为:" + result);
		System.out.println("result的二进制为:" + Integer.toString(result, 2));
	}

	private static int swap(int n) {
        // 每4位二进制可以用1位十六进制数表示
		int ou = n & 0xaaaaaaaa;// 和1010 1010 ...做与运算得到偶位
		int ji = n & 0x55555555;// 和0101 0101 ...做与运算得到奇位

		return (ou >> 1) ^ (ji << 1);
	}
}

第6题:0~1间浮点实数的二进制表示

  • 给定一个介于0和1之间的实数, (如0.625) ,类型为double,打印它的二进制表示(0.101,因为小数点后的二进制分别表示0.5,0.25.0.12…)。
  • 如果该数字无法精确地用32位以内的-进制表示,则打印“ERROR”

解题思路

在这里插入图片描述

答案如下

public class Test06 {
	public static void main(String[] args) {
		double n = 0.625;
		//double n = 0.3;
		StringBuilder builder = new StringBuilder("0.");

		while (n > 0) {
			n = n * 2;
			if (n >= 1) {
				n = n - 1;
				builder.append("1");
			} else {
				builder.append("0");
			}
		}
		
		if (builder.toString().length()>34) {
			System.out.println("ERROR");
			return;
		}
		System.out.println(builder.toString());
	}
}

第7题:出现k次与出现1次的数

数组中只有一个数出现了1次,其他的数都出现了k次,请输出只出现了1次的数。

注:如果不考虑位运算不考虑效率的话,用哈希表即可解决该题

解题思路

  • 2 个相同的2进制数做不进位加法,结果为0
  • 10个相同的10进制数做不进位加法,结果为0
  • k 个相同的k进制数做不进位加法,结果为0

答案如下

public class Test07 {
    public static void main(String[] args) {
        int[] arr = {2, 2, 2, 9, 7, 7, 7, 3, 3, 3, 6, 6, 6, 0, 0, 0};
        int len = arr.length;
        char[][] kRadix = new char[len][];
        int k = 3;

        int maxLen = 0;
        //转成k进制字符数组
        //对于每个数字
        for (int i = 0; i < len; i++) {
            //求每个数字的三进制字符串并翻转,然后转为字符数组
            kRadix[i] = new StringBuilder(Integer.toString(arr[i], k)).reverse().toString().toCharArray();
            if (kRadix[i].length > maxLen)
                maxLen = kRadix[i].length;
        }
        //不进位加法
        int[] resArr = new int[maxLen];
        for (int i = 0; i < len; i++) {
            //  不进位加法
            for (int j = 0; j < maxLen; j++) {
                if (j >= kRadix[i].length)
                    resArr[j] += 0;
                else
                    resArr[j] += (kRadix[i][j] - '0');
            }
        }
        int res = 0;
        for (int i = 0; i < maxLen; i++) {
            res += (resArr[i] % k) * (int) (Math.pow(k, i));// 8%3=2,
        }
        System.out.println(res);
    }
}