【算法學習】Bit Algorithms 總結2

前一篇:【算法學習】Bit Algorithms總結1

1. 高效判斷一個數是否是3的倍數 Check if a integer is multiple of 3 efficently

(1) 題意

寫一個函數,高效判斷一個數是否是3的倍數。

Input: 612
Output: true

(2) 思路

首先,我們想到的最平庸的方法就是直接取餘,n % 3 == 0 。不過,取餘和除法操作練習密切,而除法操作消耗的時間週期巨大,能避免則避免。

第二個方法是求出 n 的所有數位之和,判斷其是否是 3 的倍數,這個降低了除法操作的消耗,基本是 O(r)\text{O(r)} 的時間複雜度,rn 的位數。思路易得:

假設有一個兩位數ab, ab = 10 * a + b
=> ab = 9 * a + (a + b)
=> 如果ab是3的倍數,且9a也是3的倍數,則(a + b)也是3的倍數
=> 如果ab不是3的倍數,則(a + b)也不是3的倍數

上述做法可以推廣到任意位數。比如對 612 ,三位相加爲 993 的倍數,則 612 也是 3 的倍數。


不過這個方法,還是不夠快,從位運算角度可以有第三種做法。做法是——對一個數,如果其二進制形式中偶數位的 1 (set bits)和奇數位的 1 (set bits)的數量的差,是 3 的倍數,則這個數也是三的倍數。

比如,23=(00..10111) ,其偶數位的 1 有一位,奇數位的 1 有三位,差是 2,不是三的倍數,則 23 也不是三的倍數。

算法 isMultipleOf3 思路如下:

1. 求出n的絕對值 (Make n positive if n is negative)
2. 如果數n是0則返回true, 如果數n是1則返回false
3. 初始化oddCount = 0, evenCount = 0
4. 如果n != 0則循環
	a) 如果最右邊rightmost的位是1, 則oddCount++;
	b) 右移一位;
	c) 如果最右邊rightmost的位是1, 則evenCount++;
	d) 右移一位
5. return isMultipleOf3(oddCount - evenCount)

這種思路來自於第二種方法的變形。有這樣的一個題:判斷一個數是否是 11 的倍數。

假設有一個兩位數ab, ab = 10 * a + b
=> ab = 11 * a + (b - a)
=> 如果ab是11的倍數,且11a也是11的倍數,則(b - a)也是11的倍數
=>如果ab不是11的倍數,則(b - a)也不是3的倍數

三位數abc時,abc = 100 * a + 10 b + c
=> abc = 99 * a + 11 * b + (a + c - b)
=> 如果abc是11的倍數,且99a, 11b也是11的倍數,則(a + c - b)也是11的倍數

四位數abcd時, abcd = 1000 * a + 100 * b + 10 * c + d
=> abcd = 1001 * a + 99 * b + 11 * c + (d + b - c - a)
=> 如果abcd是11的倍數, 且1001a, 99b, 11c都是11的倍數, 則(b + d - a - c)也是11的倍數

上述做法可以推廣到任意位。同樣,對於二進制而言,3 就相當於十進制的 11 。這裏不做贅述。

(3) 模板代碼

//check if n is a multiple of 3 
#include <bits/stdc++.h>
using namespace std;

int isMultipleOf3(int n) {
	int oddCount = 0, evenCount = 0;
	
	/*make the number positive if it's negative.
	We are doing this to avoid stackoverflow is recursion. */
	if (n < 0) n = -n; 
	if (n == 0) return true; 
	if (n == 1) return false;
	while (n) {
		if (n & 1) ++oddCount;
		if (n & 2) ++evenCount;
		n = n >> 2;
	}
	return isMultipleOf3(abs(oddCount - evenCount));
}

int main() {
	int num = 18;
	if (isMultipleOf3(num)) printf("%d is multiple of 3.", num);
	else printf("%d is not a multiple of 3.", num);
	return 0;
}

2. 快速乘7 Efficient method to Multiply with 7

(1) 題意

使用位運算符 bitwise operator ,將一個數快速乘以 7

Input: 7
Output: 49

(2) 思路

首先,如果用普通的乘法的話,我們可能需要更多的加法和移位操作。以普通的二進制乘法器爲例,其主要操作是加法和移位操作:

  • ① 擴展被乘數爲兩倍的位寬,高 4 位爲 0 ,低 4 位爲被乘數,結果先爲全 0
  • ② 檢測乘數的最低位,如果是 1 ,說明要將結果加上被乘數;然後乘數右移(爲了檢測最低位),被乘數左移
  • ③ 檢測乘數的最低位,如果是 0 ,則直接:乘數右移,被乘數左移
  • ④ 重複 ② 或 ③ 的操作,直到乘數爲 0

示例如下:

7 * 7 = 49
被乘數7 = 0000 0111
乘數  7 = 0000 0111
① 
	0000 0111
* 		 0111
--------------
	0000 0111
② 
	0000 1110
* 		  011 
--------------
	0000 0111
+   0000 1110
③ 
	0001 1100
* 		  001 
--------------
	0000 0111
+   0000 1110
+   0001 1100
--------------
=   0011 0001 = 49

其他的乘法在乘法器中也是如此操作的。

上述乘七的過程用到了 3 次移位,3 次加法。而下面的做法可以減少這些操作:

  • 將原數左移 3 位,得到 8 * n
  • 然後減去 n ,即 8n - n = 7n

這種思路更加簡單,也可以擴展到乘任何整數

(3) 模板代碼

#include <bits/stdc++.h>
using namespace std;

long long multiplyBySeven(long long n) {
	return ((n << 3) - n);
}

int main() {
	long long n = 4;
	cout << multiplyBySeven(n) << endl;
	return 0;
}

3. 乘3.5 Multiply a given integer with 3.5

(1) 題意

給出一個整數 x ,寫一個函數將 x 乘以 3.5 ,然後返回結果的整數部分。不允許使用 %,/,*

Examples :

Input: 2
Output: 7

Input: 5
Output: 17 (Ignore the digits after decimal point)

(2) 思路和方法1

從題目看,可以使用 + ,因此,我們可以將乘以 3.5 分解爲 3.5x = 2x + x + 0.5x 。然後,使用移位操作替換乘法,寫成 (x << 1) + x + (x >> 1)

#include <bits/stdc++.h> 
//multiply a number with 3.5  
int multiplyWith3Point5(int x) { 
    return (x<<1) + x + (x>>1); 
}  
  
int main() { 
	cout << multiplyWith3Point5(2) << endl << multiplyWith3Point5(5) << endl; 
    return 0; 
} 

(3) 思路和方法2

還可以將 3.5x 寫成 (8 * x - x) / 2

int multiplyWith3Point5(int x)   
  return ((x << 3) - x) >> 1; 
}     

4. 判斷整數溢出 Check for integer overflow

(1) 題意

寫一個函數 addOvf(int &result, int a, int b),判斷 a+b 是否會溢出,如果溢出,則返回 false ;沒有溢出則返回 true 。轉換爲長整型並且相加來判斷是否溢出,是不允許的。

(2) 思路和代碼

溢出僅僅發生在兩個數同號的情況下,此時結果如果和兩個數的符號不同,則溢出。

所以,步驟如下:
① 計算和;
② 如果都是正數,結果是負數,則返回 false
③ 如果都是負數,結果是正數,則返回 false
④ 返回 true

#include <bits/stdc++.h> 
using namespace std; 

bool addOvf(int &result, int a, int b) {
	result = a + b;
	if (a > 0 && b > 0 && result < 0) return false;
	if (a < 0 && b < 0 && result > 0) return false;
	//上面兩句可以替換爲
	//if (a > INT_MAX - b || a < INT_MIN - b) return false; 
	return true;
}

int main() {  
    int res, x = 2147483640, y = 10;    
    cout << addOvf(res, x, y) << endl;
    cout << res;  
    return 0;  
}  

5. 判斷奇偶 Check whether a given number is even or odd

(1) 題意

判斷一個給定的數是奇數還是偶數。

Examples :

Input: 2 
Output: even

Input: 5
Output: odd

(2) 思路和代碼

很簡單的問題,一般不用取餘。如果是奇數,則二進制形式的位 01 ;否則是 0

#include <iostream> 
using namespace std; 
  
//returns true if n is even, else odd 
bool isEven(int n) { 
	//n & 1 is 1, then odd, else even 
	return (!(n & 1)); 
} 
   
int main() {  
	int n = 101; 
	isEven(n)? cout << "Even" : cout << "Odd"; 
	return 0; 
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章