一、前言
最近智商持續掉線,隱約有種提前犯了阿茲海默症的感覺,偶像劇看多了就是容易智商持續掉線,前一整子關注了個算法的公眾號,今天也終於撿着一篇能看懂的了,感覺非常的漲姿勢,整篇看下來覺得自己有了很大的提升,仿佛就差一點就看懂了。
以下是原文的鏈接,為了防止鏈接被破壞,為了維護漲過的姿勢還找得到的和平,阿婆,可愛又迷人的反派角色這里決定開個隨筆整理一下文章內容和評論里面的代碼,等。
二、請看正文
第一題:請聽題,要把大象關冰箱一個無序數組里有99個不重復正整數,范圍從1到100,唯獨缺少一個整數。如何找出這個缺失的整數?
解法一:
創建一個HashMap,以1到100為鍵,值都是0 。然后遍歷整個數組,每讀到一個整數,就找到HashMap當中對應的鍵,讓其值加一。阿婆止步於這一步,因為阿婆就會用JavaScript,所以阿婆打算新建個數組來着
由於數組中缺少一個整數,最終一定有99個鍵對應的值等於1, 剩下一個鍵對應的值等於0。遍歷修改后的HashMap,找到這個值為0的鍵。
假設數組長度是N,那么該解法的時間復雜度是O(2N),空間復雜度是O(N)。空間復雜度和時間復雜度這個是阿婆無腦照抄,阿婆打算有時間跪下來百度
分析:
這個解法在時間上是最優的,但是額外開辟了空間。請思考如何去降低空間復雜度?
解法二:
先把數組元素進行排序,然后遍歷數組,檢查任意兩個相鄰元素數值是否是連續的。如果不連續,則中間缺少的整數就是所要尋找的;如果全都連續,則缺少的整數不是1就是100。這個阿婆覺得搞起來太復雜,主要是阿婆不打算想破頭去排序,果斷晃棄掉了
假設數組長度是N,如果用時間復雜度為O(N*LogN)的排序算法進行排序,那么該解法的時間復雜度是O(N*LogN),空間復雜度是O(1)。
分析:
這種解法沒有開辟額外空間,但是時間復雜度又大了。請思考有沒有辦讓放時間和空間都優化?
解法三:
很簡單也很高效的方法,先算出1+2+3….+100的和,然后依次減去數組里的元素,最后得到的差,就是唯一缺失的整數。看到這里阿婆仿佛覺得很熟悉,仿佛這個題以前從哪里看過,這個解法仿佛還被阿婆深深懷疑過
假設數組長度是N,那么該解法的時間復雜度是O(N),空間復雜度是O(1)。
分析:
對於沒有重復元素的數組,這個解法在時間和空間上面是最優的了。下面請思考題目擴展...
第二題: 請聽題,一個無序數組里有若干個正整數,范圍從1到100,其中99個整數都出現了偶數次,只有一個整數出現了奇數次(比如1,1,2,2,3,3,4,5,5),如何找到這個出現奇數次的整數?
提示: 使用異或運算,在運算時相同結果為0,不同結果為1。
解法:
遍歷整個數組,依次做異或運算。由於異或在位運算時相同為0,不同為1,因此所有出現偶數次的整數都會相互抵消變成0,只有唯一出現奇數次的整數會被留下。
假設數組長度是N,那么該解法的時間復雜度是O(N),空間復雜度是O(1)。
第三題: 一個無序數組里有若干個正整數,范圍從1到100,其中98個整數都出現了偶數次,只有兩個整數出現了奇數次(比如1,1,2,2,3,4,5,5),如何找到這個出現奇數次的整數?
提示: 使用分治法(容阿婆跪去百度),把數組分成兩部分就可以回歸到上面的情況了。
解法:
遍歷整個數組,依次做異或運算。由於數組存在兩個出現奇數次的整數,所以最終異或的結果,等同於這兩個整數的異或結果。這個結果中,至少會有一個二進制位是1(如果都是0,說明兩個數相等,和題目不符)。
舉個例子,如果最終異或的結果是5,轉換成二進制是00000101。此時我們可以選擇任意一個是1的二進制位來分析,比如末位。把兩個奇數次出現的整數命名為A和B,如果末位是1,說明A和B轉為二進制的末位不同,必定其中一個整數的末位是1,另一個整數的末位是0。阿婆當時奶腦子都想炸了也沒想出來如果為1的不是最后一位怎么辦,評論區大大的代碼雕雕的表示可以人工鬧到最后一位(這個后面的代碼里沒有體現)
根據這個結論,我們可以把原數組按照二進制的末位不同,分成兩部分,一部分的末位是1,一部分的末位是0。由於A和B的末位不同,所以A在其中一部分,B在其中一部分,絕不會出現A和B在同一部分,另一部分沒有的情況。
這樣一來就簡單了,我們的問題又回歸到了上一題的情況,按照原先的異或解法,從每一部分中找出唯一的奇數次整數即可。
假設數組長度是N,那么該解法的時間復雜度是O(N)。把數組分成兩部分,並不需要借助額外存儲空間,完全可以在按二進制位分組的同時來做異或運算,所以空間復雜度仍然是O(1)。
三、評論區代碼整理
阿婆將評論區一位叫玻璃貓的大大的代碼做了一些處理(用js無腦照抄了一遍)整理出以下代碼:
1 <script> 2 //求數組內所有數的異或運算值 3 function findoutMissingDigits(arr){ 4 var result = 0; 5 for (var i in arr){ 6 result ^= arr[i]; 7 } 8 return result; 9 } 10 11 12 //這個方法的返回值用來區分兩個數,參數num是兩個數的異或運算值 13 function getShedNum(num){ 14 var numShift = 1; 15 16 /** 17 * 這個while用來判斷num從右往左數哪一位出現了1: 18 * 假如num的值是 10 = parseInt('1010',2),numShift的值是 1 = parseInt('0001',2) 19 * 執行numShift = numShift<<1; 將numShift左位移一位得到 2 = numShift = parseInt('0010',2) 20 * 直到& 結果不為零得到使兩個數異或運算結果不為1的那個數,兩個數分別&numShift 得到的結果是0或1 21 */ 22 while((num & numShift) ==0){ 23 numShift = numShift<<1; 24 } 25 26 return numShift; 27 } 28 29 function findoutMissingDigits2(arr){ 30 var A = 0; 31 var B = 0; 32 var result = findoutMissingDigits(arr); 33 //從兩個數的異或結果,獲得數組分治的“分水嶺” 34 var shedNum = getShedNum(result);//tips:到這以后如果想用右位移吧1和0放到最后一位的話就計算shedNum轉成字符串的字符串長度,右移長度-1位 35 for (var i in arr){ 36 A = ((arr[i] & shedNum) == 0)? (A^arr[i]):A; 37 B = ((arr[i] & shedNum) != 0)? (B^arr[i]):B; 38 } 39 return [A, B]; 40 } 41 42 findoutMissingDigits2([1,1,2,2,3,3,4,5,6,6,7,7])//[4,5] 43 </script>
三、總結
以下是阿婆翻閱的大量資料和使用到的姿勢點的整理,阿婆腦子都要燒掉惹,智硬~
十進制轉換成二進制:(假設把63轉換成2進制)parseInt(63).toString(2);
二進制轉換成十進制:(假設把 101110100 轉換成10進制) parseInt( "101110100",2);
異或運算 ^: ( parseInt( "1011",2) ^ parseInt( "110",2) ) == parseInt( "1101",2) //true
右位移 >>: ( parseInt( "1011",2) >> 1 ) == parseInt( "101",2) //true ( parseInt( "1011",2) >> 2 ) == parseInt( "10",2) //true
好了,接下來是聲明,如果有大大億一看到了這篇文,看到我引用了你萌的東西沒有注明出處,產生了什么不適感,請聯系我,另外,無腦照抄的我如果哪里抄的不大對億一被大大看到的話,請教(一聲)我or2