在上一次的博客中,咱們實現了使用位操做去實現四則運算。實現整數的加減乘除。此次咱們將討論位運算在算法中的一些妙用。html
在這裏我將使用題目進行示例java
1-1000這1000個數放在含有1001個元素的數組中,只有惟一的一個元素值重複,其它均只出現一次。每一個數組元素只能訪問一次,設計一個算法,將它找出來;不用輔助存儲空間,可否設計一 個算法實現?算法
這個題目有兩個要注意的點數組
那麼咱們應該怎麼去解決這個題目呢?在這裏咱們既然講了位運算,那麼確定是使用|
,&
,~
,^
等來解決這些問題。dom
首先咱們得知道:函數
A ^ A = 0 , A ^ 0 = Aui
那麼咱們能夠想一想,假如咱們將題目中的數組與 1~1000
進行異或操做那麼剩下的值就是那一個重複的值。spa
簡單的來個示例,假如數組是[1,2,3,4,3]設計
1 ^ 2 ^ 3 ^ 4 ^ 3 ^ 1 ^ 2 ^ 3 ^ 4 = 1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 3 ^ 4 ^ 4 ^ 3 = 0 ^ 3 = 3code
import java.util.Arrays;
import java.util.Random;
public class SameWord {
public static void main(String[] args) {
// 不重複的數字有1000個
int N = 1000;
// 數組的容量爲10,其中有一個爲重複的
int[] arry = new int[N + 1];
for (int i = 0; i < N; i++) {
arry[i] = i + 1;
}
Random random = new Random();
// 產生1~N的隨機數
int same = random.nextInt(N)+1;
int position = random.nextInt(N);
// 將重複的值隨機調換位置
arry[N] = arry[position];
arry[position] = same;
// 前面一部分就是爲了產生1001大小的數組,其中有一個是重複的
// 進行異或操做 【1^2^3^4……】
int x = 0;
for (int i = 0; i < N; i++) {
x = (x ^ (i+1));
}
// 對數組進行異或操做
int y = 0;
for (int i = 0; i < N + 1; i++) {
y = (arry[i] ^ y);
}
// 打印重複的值
System.out.println(x^y);
}
}
一個數組裏除了某一個數字以外,其餘的數字都出現了兩次。請寫程序找出這個只出現一次的數字。
emm,假如弄懂了上面一個題目,這個題目就垂手可得了
public void getSingle(){
int[] a = {1,2,3,2,1,3,4};
int single = 0;
for (int i : a) {
single = single^i;
}
System.out.println(single);
}
請實現一個函數,輸入一個整數,輸出該數二進制表示中1的個數
例:9的二進制表示爲1001,有2位是1
這個題目挺簡單的。有2個方向能夠去解決
1001 & 1 = 1 , 1001 >> 1 = 100,100 & 1 = 0
public void getNum(){
int n = 255;
int count = 0;
while(n!=0){
if((n & 1) == 1){
count ++;
}
n = n>>1;
}
System.out.println("個數是:"+count);
}
這種解法其實有必定問題的,由於若是去移動負數的話就會涼涼,陷入死循環(負數右移,最左邊的那個1會一直存在)。那麼咱們怎麼解決這個方法呢?既然咱們不能移動n,那麼咱們能夠移動相與的那個數啊
1001 & 1 = 1, 1<<1 = 10,1001&10 = 0
public void getNum2(){
int n = 222;
int flag = 1;
int count = 0;
while(flag >=1){
// 這個地方不是n&flag == 1了
if((n&flag) > 0){
count ++;
}
flag = flag << 1;
}
System.out.println("個數是:"+count);
}
咱們能夠去考慮下這個的時間複雜度。實際上,不管你要求解的數值有多小,它都要循環32次(由於int爲4個字節,須要循環32次)。
最高效的解法
這邊有個規律:n&(n-1)可以將n的最右邊的1去掉。
那麼根據這個規律,若是咱們將右邊的1去掉,去掉的次數也就是二進制中1的個數
public void getNum3(){
int n = 233;
int count = 0;
while(n>0){
count ++;
n = (n -1)&n;
}
System.out.println("個數是:"+count);
}
求平均值咱們通常是使用相加來進行操做的,可是若是值比較大呢,形成溢出怎麼辦?實際上咱們知道溢出就是由於進位形成的,那麼咱們就能夠使用位來解決這個方法。
10 二進制 1010
14 二進制 1110
公共部分: 1010
不一樣部分的和: 0100
不一樣部分除以2:0010
平均數 = 1010(相同部分) + 0010(不一樣部分的平均數) = 1100
所以兩者平均數爲12
以上的操做咱們能夠用位運算來替代:
公共部分 = a & b
不一樣部分的平均值 = (a ^ b) >> 1
平均值 = 公共部分 + 不一樣部分的平均值 = (a & b) + ((a ^ b) >> 1)
public void aver(){
int a = 10;
int b = 220;
int averNum = (a&b) + ((a^b)>>1);
System.out.println("平均值是:"+averNum);
}
給出一個16位的無符號整數。稱這個二進制數的前8位爲「高位」,後8位爲「低位」。如今寫一程序將它的高低位交換。例如,數34520用二進制表示爲:
10000110 11011000
將它的高低位進行交換,咱們獲得了一個新的二進制數:
11011000 10000110
它便是十進制的55430A | 0 = A
在這個題目(以34520爲例)中咱們能夠先將 10000110 11011000 >> 8
右移動8位獲得A = 00000000 10000110
,10000110 11011000 << 8
獲得B = 11011000 00000000
,而後A | B = 11011000 10000110