1. 程式人生 > >位運算基礎與面試題C++

位運算基礎與面試題C++

共有如下幾種位運算

運算子 功能 用法
~ 位取反 ~expr
<< 左移 expr1<<expr2
>> 右移 expr1>>expr2
& 位與 expr1&expr2
^ 位異或 expr1^expr2
| 位或 expr1

如果運算物件是帶符號的並且符號為負,那麼位運算如何處理運算物件的“符號位”依賴於機器,並且,左移可能會改變符號位的值。
左移運算物件可能會使物件發生提升,將char的8位變成int的32位。
>>有符號物件時,可能在左側插入符號位的副本,也可能插入值為0的二進位制位。

示例一

網頁黑名單系統、垃圾郵件過濾系統、爬蟲的網址判斷重複系統, 並且系統容忍一定程度的失誤率, 但是對空間的要求比較嚴格。=》布隆過濾器。

布隆過濾器

一個布隆過濾器精確地代表一個集合,可以精確地(非準確,存在誤判)判斷一個元素是否在集合中,精確程度取決於使用者的具體設計,但做到100%的精確是不可能的。
布隆過濾器的優勢在於使用很少的空間可以做到精確率較高。

假設有一個長度為m的bit型別陣列記為bitarray,其中每個為止只佔一個bit,只有0白,1黑兩種狀態。假設一共有k個雜湊函式,函式的輸出 m \ge m ,並且k個雜湊函式足夠的優秀彼此之間完全獨立,則每一個物件經過k個雜湊函式算出的結果也是相互獨立的,可能相同也可能不同,對算出的每一個結果,對m取餘,取餘的結果在bitarray相應的位置寫為1圖黑,接下來處理所有的物件。對已經圖黑的位置,讓其繼續為黑即可,至此一個布隆過濾器就生成了。這個布隆過濾器代表之前所有物件組成的集合。
假設一個物件為a,想檢查a是否在布隆過濾器中,只用將a通過k個雜湊函式,然後對所有結果m取餘,然後對比bitarray的位置,如果全部為黑1,則這個物件判斷為在布隆過濾器中,只要有一個位置不為1,則不在布隆過濾器中。

假設bitarray大小為m,雜湊函式個數k,樣本數量為n,失誤率為p。
通常題目有n,p值,計算m的公式為
m = n × ln p ( ln 2 ) 2 , m = -\frac{n\times\ln p}{(\ln2)^2},
通常將m向上取整。雜湊函式個數k的計算公式為
k = ln 2 × m n , k = \ln 2\times \frac{m}{n},
計算最終的失誤率公式為
p = ( 1 e n k m ) k . p = (1-e^{-\frac{nk}{m}})^k.

設計過程:
1.根據樣本數量n與失誤率p計算過濾器大小m。
2.根據過濾器大小m以及樣本數量n計算雜湊函式個數k。
3.計算最終的失誤率p。

例題二

如何不用任何的額外變數交換兩個整數的值。
分析:

a = a^b;
b = a^b;
a = a^b;

上述可理解為

b = a0^b0^b0;
a = a0^b0^a0;

例題三

給定兩個32位整數a和b,返回a和b中較大的,但是不能用任何比較判斷。
分析:
思路一:得到a-b的符號位。
當a-b溢位時,可能會發生錯誤。

int sign(int n) {
	return (n >> 31) & 1;
}

int maxNumber(const int &a, const int &b) {
	int c = a - b;
	int signC = sign(c);
	return signC * b + (1 - signC)*a;
}

思路二:判斷a,b是否異號

int sign(int n) {
	return (n >> 31) & 1;
}

int maxNumber(const int &a, const int &b) {
	int signA = sign(a);
	int signB = sign(b);
	int diff = signA ^ signB;
	int c = a - b;
	int signC = sign(c);
	return diff * ((1 - signA)*a + (1 - signB)*b) + (1 - diff)*(signC*b + (1 - signC)*a);
}

例題四

給定一個整形陣列arr,其中只有一個數出現了奇數次,其他的數都出現了偶數次,請列印這個數,要求時間複雜度為 O ( N ) O(N) ,額外空間複雜度為 O ( 1 ) O(1)
分析:
異或的利用。
1.n與0異或得n
2.n與n異或得0
3.異或運算滿足交換律
4.異或運算滿足結合律

異或得順序可以任意重排,不會改變原來的結果。
若arr為[A,B,C,A,B,C,D]
可等價於異或[A,A,B,B,C,C,D]
讓一個變臉eo依次異或arr陣列中的數,最終得到的結果為出現了奇數次的數。

int oddNumber(int *arr, const int &length) {
	int eo = 0;
	for (int i = 0; i < length; ++i) {
		eo ^= arr[i];
	}
	return eo;
}

例題五

給定一個數組arr,其中有兩個數出現奇數次,其餘數出現偶數次,找到這兩個奇數次的數。要求時間複雜度為 O ( N ) O(N) ,額外空間複雜度為 O ( 1 ) O(1)
分析:利用例題四的方法,找到eo=a^b,在eo中找到為1的位元位,然後用這一位篩選arr陣列中的數,再申請一個eo2去異或篩選出的數即為第一個奇數次的數,然後eo^eo2為第二個奇數次的數。

void twoOddNumber(int *arr, const int &length, int ans[2]) {
	int eo = 0;
	for (int i = 0; i < length; ++i) {
		eo ^= arr[i];
	}
	int index = findOneBit(eo);
	if (index < 0) {
		return;
	}
	else {
		int eo2 = 0;
		for (int i = 0; i < length; ++i) {
			if ((arr[i] >> index) & 1) {
				eo2 ^= arr[i];
			}
		}
		ans[0] = eo2;
		ans[1] = eo2 ^ eo;
	}
	
}

例題六

請設計一種加密過程,完成對明文text的加密和解密工作。
分析:異或運算可以完成簡單的加密與解密過程。
明文text,使用者給定密碼pw,假設密文為cipher

cipher = text ^ pw
text = cipher ^ pw = (text ^ pw) ^ ps
	=text ^ (pw ^ pw)=text