算法之美 : 位運算

上一小節我們用三道題了解一下面試過程中棧和隊列的常見面試題。本小節筆者將通過幾個 位運算 的題目來帶大家熟悉下常用的位運算知識。

相比于棧和隊列來講,筆者自身認為位運算需要掌握的知識就要多一些,包括對于數字的二進制表示,二進制的反碼,補碼。以及二進制的常見運算都需要了解。當然如果系統的去學,可能沒有經歷,也可能即使學完了,仍舊不會做題。所以筆者認為通過直接去刷一些相應的題目,則是一個比較便捷的途徑。

給定一個整數,請寫一個函數判斷該整數的奇偶性(?????)

該題目作為后續題目的鋪墊,看上去還是沒有任何難度的。主要考察了面試能否想到用二進制的位運算方法去解決。

首先整數可以分為正數,負數,0。也可以分為奇數和偶數。偶數的定義是:如果一個數是2的整數倍數,那么這個數便是偶數。如果不使用位運算的方法,我們完全可以使用下面的方式解決:

public boolean isOdd(int num){//odd 奇數return num % 2 != 0;
}
復制代碼

可是面試題不可能去簡單就考察這么簡單的解法,進而我們想到了二進制中如果 一個數是偶數那么最后一個一定是 0 如果一個數是奇數那么最后一位一定是 1;而十進制 1 在 8 位二進制中表示為 0000 0001,我們只需將一個數個 1相與(&) 得到的結果如果是 1 則表示該數為奇數,否知為偶數。所以這道題的最佳解法如下:

public boolean isOdd(int num){return num & 1 != 0;
}
復制代碼
#include "iostream"  
using namespace std;  
//聲明
bool IsOdd(int num);bool IsOdd(int num)
{int res = (num & 1);return res != 0;
}
復制代碼

測試:

int main(int argc, const char * argv[]) {std::cout << "是否是奇數 : " << IsOdd(1) <<endl;std::cout << "是否是奇數 : " << IsOdd(4) <<endl;return 0;
}//結果
是否是奇數 : 1//是 true
是否是奇數 : 0//不是 false
復制代碼

同樣給定一個整數,請寫一個函數判斷該整數是不是2的整數次冪(?????)

這道題仍舊考察面試者對于一個數的二進制的表示特點,一個整數如果是2的整數次冪,那么他用二進制表示完肯定有唯一一位為1其余各位都為 0,形如 0..0100...0。比如 8 是 2的3次冪,那么這個數表示為二進制位 0000 1000 。

除此之外我們還應該想到,一個二進制如果表示為 0..0100...0,那么它減去1得到的數二進制表示肯定是 0..0011..1 的形式。那么這個數與自自己減一后的數相與得到結果肯定為0。

如:

所以該題最佳解法為:

public boolean log2(int num){return (num & (num - 1)) == 0;
}
復制代碼
#include "iostream"  
using namespace std;  
//聲明
bool IsLog2(int num);
//定義
bool IsLog2(int num)
{return (num & (num -1)) == 0;
}
復制代碼

測試:

int main(int argc, const char * argv[]) {std::cout << "是否是2的整數次冪 : " << IsLog2(1) <<endl;std::cout << "是否是2的整數次冪 : " << IsLog2(3) <<endl;return 0;
}//結果
是否是2的整數次冪 : 1 //是 true
是否是2的整數次冪 : 0 //不是 false
復制代碼

給定一個整數,請寫一個函數判斷該整數的二進制表示中1的個數(?????)

此題較之上一題又再進一步,判斷一個整數二進制表示中1的個數,假設這個整數用32位表示,可正可負可0,那么這個數中有多少個1,就需要考慮到符號位的問題了。

相信讀者應該都能想到最近基本的解法即通過右移運算后與 1 相與得到的結果來計算結果,如果采用這種解法,那么這個題的陷阱就在于存在負數的情況,如果負數的話標志位應該算一個1。所以右移的時候一定要采用無符號右移才能得到正確的解法。

ps 對于正數右移和無符號右移得到結果一樣,如果是負數,右移操作將在二進制補碼左邊添加追加1,而無符號右移則是補 0 。

所以此題一種解法如下:

public int count1(int n) {int res = 0;while (n != 0) {res += n & 1;n >>>= 1;}return res;
}
復制代碼
#include "iostream"  
using namespace std;//注意C++中沒有無符號右移操作,所以這里傳入一個 unsigned 數作為 params
int count1(unsigned int n){int res = 0;while(n != 0){res += n & 1;n >>= 1;}return res;
}
復制代碼

測試結果:

int main(int argc, const char * argv[]) {std::cout << "二進制中1的個數 : " <<  count1(-1) <<endl;std::cout << "二進制中1的個數 : " <<  count1(1) <<endl;return 0;
}//結果
二進制中1的個數 : 32
二進制中1的個數 : 1
復制代碼

能回答出上邊的答案你的面試肯定是及格了,但是作為練習來說,是否有額外的解法呢?首先上述結果最壞的情況可能需要循環32次。上面我們算過一道如何判斷一個數是否是2的整數倍,我們用過了 n&(n-1)==0 的方法。其實該題的第二個解法也可以用這個方法。為什么呢?我們開看一次上邊的圖:

我們是否能發現,每次與比自己小1的數與那么該數的二進制表示最后一個為1位上的1將將會被抹去。其實這是一個知道有這種原理才能想到的方法,所以大家也不用哀嘆說我怎么想不到,通過這次記住有這個規律下次就多一個思路也不是很么壞事。

下面我們來看下判斷一個數中有多少個1的完整圖解:

所以我們可以通過如下方法來得到題解,這樣我們可以減少移動次數

public int countA(int n){int res = 0;while(n != 0){n &= (n - 1);res++;}return res;
}
復制代碼
#include "iostream"  
using namespace std;  
// 同上傳入無符號整數 
int countA(unsigned int n){int res = 0;while(n != 0){n &= (n - 1);res++;}return res;
}
復制代碼

測試結果:

int main(int argc, const char * argv[]) {std::cout << "二進制中1的個數 : " <<  countA(-1) <<endl;std::cout << "二進制中1的個數 : " <<  countA(1) <<endl;return 0;
}//結果
二進制中1的個數 : 32
二進制中1的個數 : 1
復制代碼

在其他數都出現兩次的數組中找到只出現一次的那個數(?????)

這道題同樣是考察為位運算的一道題,但是如果對于不熟悉位運算的朋友可能壓根都不會往這方面想,也許當場直接就下邊寫下了遍歷數組記每個數出現次數的代碼了。其實這道題要求在時間復雜度在O(n) 空間復雜度為O(1)的條件下,那種解法是不符合要求的。我們來看下為位運算的解題思路。

首先我們應該知道二進制異或操作,異或結果是二進制中兩個位相同為0,相異為1。因此可以有個規律:

任何整數 n 與 0 異或總等于其本身 n,一個數與其本身異或那么結果肯定是 0。

還需要知道一個規律:

多個數異或操作,遵循交換律和結合律。

對于第一條朋友們肯定都很好理解,然而第二條規律才是這道題的解題關鍵。如果我們有一個變量 eO = 0 那么在遍歷數組過程中,使每個數與 eO 異或得到的值在賦值給額 eO 即 eO=eO ^ num 那么遍歷結束后eO的值一定是那個出現一次的數的值。這是為什么呢?我們可以舉個例子:

假設有這么一個序列: C B D A A B C 其中只有 D 出現一次,那么因為異或滿足交換律和結合律,所以我們遍歷異或此序列的過程等價于

eO ^ (A ^ A ^ B ^ B ^ C ^ C ) ^ D = eO ^ 0 ^ D = D
復制代碼

所以對于任何排列的數組,如果只有一個數只出現了奇數次,其他的數都出現了歐數次,那么最終異或的結果肯定為出現奇數次的那個數。

所以此題可以有下面的這種解法:

java 解法

public int oddTimesNum(int[] arr) {int eO = 0;for (int cur : arr) {eO = eO ^ cur;}return eO;
}
復制代碼

C++ 解法

int oddTimesNum(vector<int> arr) {int eO = 0;for (int cur : arr) {eO = eO ^ cur;}return eO;
}
復制代碼

測試:

int main(int argc, const char * argv[]) {vector<int>  arr = {2,1,3,3,2,1,4,5,4};std::cout << "出現奇數次的那個數: " << oddTimesNum(arr) <<endl;return 0;
}//結果
出現奇數次的那個數: 5
復制代碼

關于這道題還有個延伸版本,就是如果數組中出現1次的數有兩個,那么該如何得到這兩個數。

在其他數都出現兩次的數組中找到只出現一次的那兩個數(?????)

我們順著上題的思路來思考,如果有兩個數獲得的結果 eO 肯定是 eO = a^b,此題的關鍵就在于如何分別得到 a,b 這兩個數。我們應該想到,任何不相同的兩個除了跟自己異或外,不可能每一個位都相同,也就是說不相同的兩個數 a b 異或得到結果二進制表示上肯定有一位為 1。 這是關鍵。

我們可以假設第 k 位不為 0 ,那么就說明 a 與 b 在這位上數值不相同。我們要做只是設置一個數第 k 位 為 1,其余位為 0 記為 rightOne

這時需要拿 eOhasOne = 0 再異或遍歷一次數組,但是需要忽略與 rightOne 相與等于 0 的數。因為相與等于 0 則代表了這個數肯定是兩個數中第 k 位不為 1的那個。最終得到的 eOhasOne 就是 a b 中第 k 為為 1 的那個。

那么接下來就剩下一個問題要解決了,如何找到 rightOne ,這里采用與本身補碼相與的方法得到即 int rightOne = eO & (~eO + 1)

可以參照下圖來理解下整個過程:

我們來看下最終的代碼:

java 寫法

public void printOddTimesNum(int[] arr) {int eO = 0;int eOhasOne = 0;for (int cur : arr) {eO = eO ^ cur;}int rightOne = eO & (~eO + 1);for (int cur : arr) {if ((rightOne & cur) != 0) {eOhasOne = eOhasOne ^ cur;}}System.out.println("eOhasOne = " + eOhasOne + "  " + (eOhasOne ^ eO));
}
復制代碼

C++ 寫法

void printOddTimesNum(vector<int> arr) {int eO = 0;int eOhasOne = 0;for (int cur : arr) {eO = eO ^ cur;}int rightOne = eO & (~eO + 1);for (int cur : arr) {if ((cur & rightOne) != 0) {eOhasOne = eOhasOne ^ cur;}}std::cout<<"一個出現1次的數 " << eOhasOne << endl;std::cout<<"二個出現1次的數 " << (eO ^ eOhasOne) <<endl;
}
復制代碼

測試:

int main(int argc, const char * argv[]) {vector<int>  arr1 = {2,1,3,3,2,1,4,5};printOddTimesNum(arr1);return 0;
} //結果:
一個出現1次的數 5
二個出現1次的數 4
復制代碼

參考

《劍指 offer 第二版》 《程序員代碼面試指南 - 左程云》

歡迎關注我的微信公眾號,接收第一手技術干貨

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/390072.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/390072.shtml
英文地址,請注明出處:http://en.pswp.cn/news/390072.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

447. 回旋鏢的數量

447. 回旋鏢的數量 給定平面上 n 對 互不相同 的點 points &#xff0c;其中 points[i] [xi, yi] 。回旋鏢 是由點 (i, j, k) 表示的元組 &#xff0c;其中 i 和 j 之間的距離和 i 和 k 之間的距離相等&#xff08;需要考慮元組的順序&#xff09;。 返回平面上所有回旋鏢的…

一名3年工作經驗的程序員應該具備的技能

本文轉自:https://m.imooc.com/article/details?article_id7557 前言 因為和同事有約定再加上LZ自己也喜歡做完一件事之后進行總結&#xff0c;因此有了這篇文章。這篇文章大部分內容都是面向整個程序員群體的&#xff0c;當然因為LZ本身是做Java開發的&#xff0c;因此有一部…

js 排序算法總結

1.冒泡排序 平均時間復雜度O(N2) 最好情況O(N)最壞情況O(N2) 空間復雜度O(1) function bubbleSort(arr){if(arr.length < 1)return arr;var flag 1; // 標識是否進行交換for(var i0; i < arr.length; i){if(i !0 && flag) break;for(var j0; j <…

524. 通過刪除字母匹配到字典里最長單詞

524. 通過刪除字母匹配到字典里最長單詞 給你一個字符串 s 和一個字符串數組 dictionary 作為字典&#xff0c;找出并返回字典中最長的字符串&#xff0c;該字符串可以通過刪除 s 中的某些字符得到。 如果答案不止一個&#xff0c;返回長度最長且字典序最小的字符串。如果答案…

django開發商城(提供初始數據,商城首頁及購物車)

1.爬取數據 2.json數據轉化為sql語句 3.新建輪播圖模型(模型名與sql語句對應表名相同) class Wheel(models.Model):imgmodels.CharField(max_length150)namemodels.CharField(max_length20)trackidmodels.CharField(max_length20) 4.終端打開mysql,執行插入語句 5.在首頁進行展…

多語言版希爾排序

2019獨角獸企業重金招聘Python工程師標準>>> 簡介 希爾排序(Shells Sort)是插入排序的一種又稱“縮小增量排序”&#xff08;Diminishing Increment Sort&#xff09;&#xff0c;是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。該方法因D.L…

UML 中extend和include的區別

在UML用例圖中有兩種關系——包含和擴展&#xff0c;容易混淆&#xff0c;下面通過一張表來區別一下這兩種關系。 轉載于:https://www.cnblogs.com/yonyong/p/8555547.html

hdu 6301 Distinct Values(貪心)題解

題意&#xff1a;長為n的串&#xff0c;給你m個區間&#xff0c;這些區間內元素不重復&#xff0c;問這樣的串字典序最小為&#xff1f; 思路&#xff1a;用set保存當前能插入的元素&#xff0c;這樣就能直接插入最小元素了。對操作按l排序&#xff0c;因為排過的不用排&#x…

瀏覽器兼容CSS漸進增強 VS 優雅降級如何選擇

由于低級瀏覽器不支持 CSS3&#xff0c;但是 CSS3 特效太優秀不忍放棄&#xff0c;所以在高級瀏覽器中使用CSS3&#xff0c;而在低級瀏覽器只保證最基本的功能。二者的目的都是關注不同瀏覽器下的不同體驗&#xff0c;但是它們側重點不同&#xff0c;所以導致了工作流程上的不同…

細數sass安裝中遇到的坑

前言&#xff1a; 前兩天打算清理電腦的時候&#xff0c;遇到了一點特殊的問題&#xff0c;打算重裝一些東西&#xff0c;其中就有我一直用的順手的SASS預編譯工具。 但是在重裝的時候&#xff0c;我發現我居然不會用了&#xff1f;&#xff1f;&#xff1f; 靠&#xff0c;要不…

442. 數組中重復的數據

442. 數組中重復的數據 給定一個整數數組 a&#xff0c;其中1 ≤ a[i] ≤ n &#xff08;n為數組長度&#xff09;, 其中有些元素出現兩次而其他元素出現一次。 找到所有出現兩次的元素。 你可以不用到任何額外空間并在O(n)時間復雜度內解決這個問題嗎&#xff1f; 示例&am…

[bzoj 2726] 任務安排 (斜率優化 線性dp)

3月14日第三題&#xff01;&#xff01;&#xff01;&#xff08;雖然是15號發的qwq&#xff09; Description 機器上有N個需要處理的任務&#xff0c;它們構成了一個序列。這些任務被標號為1到N&#xff0c;因此序列的排列為1,2,3…N。這N個任務被分成若干批&#xff0c;每批…

2018年,牛客網小白月賽5

第一次啊&#xff0c;補題&#xff0c;希望大佬批評。 題目按我補題順序來的。 https://www.nowcoder.com/acm/contest/135#question H 題 最大公倍數 題意:給出兩個數&#xff0c;求最大公倍數 歐幾里德算法算出最大公約數k; 然后算出。最大公倍數即可 代碼如下&#xff1a; …

292. Nim 游戲

292. Nim 游戲 你和你的朋友&#xff0c;兩個人一起玩 Nim 游戲&#xff1a; 桌子上有一堆石頭。你們輪流進行自己的回合&#xff0c;你作為先手。每一回合&#xff0c;輪到的人拿掉 1 - 3 塊石頭。拿掉最后一塊石頭的人就是獲勝者。 假設你們每一步都是最優解。請編寫一個函…

0710 mux協議的作用(ppp撥號時如何和gprs進行at指令交互)

ppp撥號使gprs上網的同時如何和gprs模塊進行at指令的交互&#xff0c;這是一個問題。 在linux中&#xff0c;ppp撥號上網是內核中支持的&#xff0c;只需要在內核配置中選上。 ppp撥號的方式使gprs進行上網與at指令使gprs上網&#xff0c;兩者之間有不同。ppp是一個將用at指令使…

爬蟲筆記(十二)——瀏覽器偽裝技術

為什么要進行瀏覽器偽裝技術&#xff1f; 有一些網站為了避免爬蟲的惡意訪問&#xff0c;會設置一些反爬蟲機制&#xff0c;對方服務器會對爬蟲進行屏蔽。常見的飯爬蟲機制主要有下面幾個&#xff1a; 1. 通過分析用戶請求的Headers信息進行反爬蟲 2. 通過檢測用戶行為進行反…

650. 只有兩個鍵的鍵盤

650. 只有兩個鍵的鍵盤 最初記事本上只有一個字符 ‘A’ 。你每次可以對這個記事本進行兩種操作&#xff1a; Copy All&#xff08;復制全部&#xff09;&#xff1a;復制這個記事本中的所有字符&#xff08;不允許僅復制部分字符&#xff09;。Paste&#xff08;粘貼&#x…

Codeforces 626F Group Projects (DP)

題目鏈接 8VC Venture Cup 2016 - Elimination Round 題意 把$n$個物品分成若干組&#xff0c;每個組的代價為組內價值的極差&#xff0c;求所有組的代價之和不超過$k$的方案數。 考慮DP&#xff0c;$f[i][j][k]$表示考慮到第$i$個物品的時候&#xff0c;還有$j$組尚未分配完…

《活出生命的意義》:人生有何意義?

在你一生的閱讀體驗中&#xff0c;如果能夠有一本書&#xff0c;它的某個章節、某種思想、或者某句話能夠觸動你的內心&#xff0c;解決你的困惑&#xff0c;甚至能改變你的命運&#xff0c;那這樣的一本書你一定要視如珍寶&#xff0c;經常翻閱&#xff0c;維克多弗蘭克爾的《…

右鍵添加git-bash

主要&#xff1a; 右鍵如果沒有git-bash&#xff0c;如何給右鍵手動添加 前面對右鍵存在git-bash但使用出現問題的解決&#xff0c;也想到如果右鍵都沒有&#xff0c;該如何給右鍵添加了&#xff0c;于是接著記錄下如何添加的過程&#xff1a; 情形&#xff1a; 手動給右鍵添加…