從面試題中學演算法(2)---求陣列中唯一n個出現1次的數字(n=1,2,3)
題目要求:給定一個整數陣列,其中只有1個數出現1次,其他數字都出現偶數次。找出這個數字。如果其中只有2個數字出現1次呢?3個數字出現一次呢?依次找出。
例子:陣列a[] ={1,2,3,4,2,1,3} 那麼程式輸出 僅出現一次的數字為4。陣列a[] = {1,2,3,2,3,4}.那麼程式輸出2個僅出現一次的數字為1,4. 陣列a[]= {1,2,3,3,2,4,6}那麼程式輸出 3個僅出現一次的數為1,4,6.
好了,題目理解清楚了,下面就考慮解法了。首先,說明一個位運算---異或。這個對解題非常重要。異或是位運算的一種,指的是兩個數字的二進位制,每個位逐一異或。兩個位相同則該位為0,不同則為1。異嘛,肯定不同為1.比如:3 ^ 4 = (011)^(100) =(111)。並且異或滿足交換律。這個交換律很重要。x=a^b^c=a^c^b。0和x異或都為x。
下面說明3個非常重要的等式。
1. -n = (~n+1) = ~(n-1)
一個數取負數運算將等於該數按位取反加1,並且也等於該值減1取反。從補碼角度理解,第二個式子恰好為負數的補碼(負數的補碼為原始碼取反加1)對應於n而言,其負數就為-n嘛。
2.保留一個數字的最右邊為1的位,其他位都置為0. n = n &(~n+1 )
比如n為十進位制 7,對應二進位制為0111.那麼保留該數字的最右邊1為: n = n &(~n+1 )=0001。可見只保留了最右邊位為1的位。
3.將一個數的最右邊1變為0,其餘的位不變。 n = n &(n-1) .比如n為 十進位制7,對應二進位制為0111.n = n &(n-1) =0110。僅將最右邊為1的位變為0。
一.找出1個僅出現一次的數字
在陣列中,只有一個數字出現一次,其他數字都成對出現,那麼如果對整個陣列進行異或操作呢?由於異或操作滿足交換律,而兩個相同的數字之間異或為0,而0和任意數異或都為任意數。所以遍歷一遍陣列,且依次將結果異或,最後的異或值就是所查詢的數字。比如a[] ={1,2,3,4,2,1,3} xorRst 為異或值。將陣列中所有元素異或下。xorRst =1^2^3^4^2^1^3。根據交換律xorRst =(1^1)^(2^2)^(3^3)^4其中括號內的值都為0.所以xorRst =0^0^0^4=4.即是結果。程式碼如下:
int FindSingleNum(int a[],int len)
{
int Rst = 0;
if(a != NULL && len > 0)
{
for(int i=0 ; i<len; ++i)
Rst ^= a[i];
}
return Rst ;
}
二.找出2個僅出現一次的數字
那麼問題升級為陣列中有2個僅出現一次的數字。既然我們有了上面的分析,那麼2個數字的能不能按照上面思路走呢?如果我們找到了區分這個2個數字的規則,就可以將該2個數字分到2組中去,而對於每個組而言,都可以按照出現1個數字的方法解決。那麼問題就解決了。那現在是如何區分這個兩個數字呢?我們還是將所有元素進行異或後結果。xorRst = a^b。(a,b為僅出現1次的2個數,其他成對的數都異或為0了,見前面分析)。由於a,b不同,所以a,b的對應的每個位至少有一個位不同,我們如果找出這個了位,那麼a,b就可以依次分入到兩個不同的組中去。而xorRst的最右邊為1的數(如何求就是前面介紹的3個等式之一)。就完全表示。假設xorRst最右邊為1的位是第m位。則a,b的第m位肯定不同。依次來將陣列元素進行分組,相同的數字肯定是會分入到同一組中(因為相同的數每位都相同)。
int LastBitof1(int num)
{
return num & (~num+1) ;
}
bool FindTwoOnceNum(int a[],int len,int &first,int &second)
{
bool RstState = false ;
if(a != NULL && len > 0)
{
int xorRst = 0 ;
for(int i= 0 ; i < len ; ++i)
xorRst ^= a[i] ;
xorRst = xorRst & (~xorRst +1 );//get last bit of 1
first = second = 0 ;
for(int j=0 ;j<len ;++j)
{
if(a[j] & xorRst ) //通過xorRst 來分組
first ^= a[j];
else
second ^= a[j];
}
RstState = true ;
}
return RstState ;
}
三.找出3個僅出現一次的數字
那麼問題如果在升級,一個陣列中有3個僅出現一次的數字呢?有了前面兩個問題的思路,我們猜想,也就是該如何進行分組?將a,b,c三個數如果分入到2個組中,分別對兩組求解異或值,其中一組的異或結果就是a,b,c中的一個,有了這個,那麼問題就回到第二個問題了。也就解決了。那麼關鍵是如何進行a,b,c的分組呢?如何找到一個卻別a,b,c的方法呢?
依然繼續將陣列中所有的元素進行異或求值。其結果為 xorRst = a^b^c(a,b,c為三個僅出現一次的數)。
下面將開啟一連串的邏輯推導。
1.由於a,b,c三個數互不相等。所以xorRst與a,b,c都不相等。
證明:假設xorRst與a相等。 則 a = a^b^c.那麼 b^c=0 ,即是 b等於c,與a,b,c互不相等矛盾。
2.因為xorRst與a,b,c都不相等。所以xorRst^a,xorRst^b,xorRst^c都不為0.(兩個不同數異或結果肯定不為0)
3.假設有這樣一個函式f(n)=lastBifof1(n)(問題2程式碼中的一個函式),f(n)函式目的就是保留數n的最右邊1位,其他位都置為0。這是前面提到的3個等式中的一個。所以看下:這樣的結果
f(xorRst ^a ) ^f(xorRst ^b ) ^f(xorRst ^c ) 。這個式子意思就是,分別求xorRst^a,xorRst^b,xorRst^c最右邊為1的位。然後將結果異或起來。從二進位制角度看,其實也就是 三個只有一個位為1的數進行異或。因為f(xorRst ^a ) , f(xorRst ^b ) ,f(xorRst ^c ) 三個數都不為0.所以f(xorRst ^a ) ^ f(xorRst ^b ) ^f(xorRst ^c ) 結果也不為0(如果該結果為0,那麼至少有一個f(xorRst^n)(n=a,b,c)為0,與前面f(xorRst^a,b,c)不為0矛盾。
所以該條結論:A= f(xorRst ^a ) ^ f(xorRst ^b ) ^f(xorRst ^c ) 不為0. 即是至少有一位為1.
4. 假設A的第m位為1,那麼得出的結論就是 xorRst ^a,xorRst^b,xorRst^c 這三個數中僅有一個數的第m位為1或者三個數的第m位都為1.(這是很好得出的 1=1^1^1或者 1=0^1^0 就這兩種情況)。
下面證明:xorRst ^a,xorRst^b,xorRst^c 三個數的第m位都為1的情況是不可能的。
證明:假設xorRst^a,xorRst^b,xorRst^c 第m位都為1.那麼xorRst 的第m位都與a,b,c的第m位相反(位不同才為1嘛)。那麼也就是a,b,c的第m位都相同。
4.1 假設a,b,c的第m位都為0.因為xorRst與a,b,c在第m位都相反,所以 xorRst 的第m位為1.有因為xorRst=a^b^c。所以在xorRst在第m位上是為0的。與前面矛盾。
4.2 假設a,b,c的第m位都為1.因為xorRst與a,b,c在第m位都相反,所以 xorRst 的第m位為0,又因為xorRst=a^b^c 所以xorRst的第m位為1.與前面的矛盾。
5.結論:xorRst ^a,xorRst^b,xorRst^c 這三個數中僅有一個數的第m位為1。由此找到了區分a,b,c三者的方法。那麼依據xorRst ^n的第m位來分組a,b,c三個數進入到兩個不同組中去。
void Swap(int &a,int &b)
{
int tmp = a;
a = b;
b = tmp;
}
bool FindTreeOnceNum(int a[],int len, int &first,int &second, int &third)
{
bool RstState = false ;
if(a != NULL && len > 0)
{
int xorRst = 0,i;//異或結果
for(i=0; i<len; ++i)
xorRst ^= a[i];
//下面求 f(x^a)^f(x^b)^f(x^c)的最右邊為1的第m位
int flag =0;
for(i=0 ; i<len ; ++i)
flag ^= LastBitof1(xorRst ^ a[i]);
flag = LastBitof1(flag); //只保留最右邊為1的位
//求first
first = second = third = 0 ;
vector<int> vec;
for(i=0 ; i<len ;++i)
if(LastBitof1( xorRst ^ a[i] ) == flag )//x^a的第m位為1的情況 這裡的m位其實是最右邊為1位
first ^= a[i]; //這樣first 就求出了
for(i=0 ; i<len ;++i)
if( a[i] == first)
Swap(a[i],a[len-1]);//移除該first值
FindTwoOnceNum(a,len-1,second,third);
RstState = true;
}
return RstState ;
}
四.完整程式碼及其測試
#include <iostream>
#include <vector>
using namespace std;
int FindSingleNum(int a[],int len)
{
int Rst = 0;
if(a != NULL && len > 0)
{
for(int i=0 ; i<len; ++i)
Rst ^= a[i];
}
return Rst ;
}
int LastBitof1(int num)
{
return num & (~num+1) ;
}
bool FindTwoOnceNum(int a[],int len,int &first,int &second)
{
bool RstState = false ;
if(a != NULL && len > 0)
{
int xorRst = 0 ;
for(int i= 0 ; i < len ; ++i)
xorRst ^= a[i] ;
xorRst = xorRst & (~xorRst +1 );//get last bit of 1
first = second = 0 ;
for(int j=0 ;j<len ;++j)
{
if(a[j] & xorRst ) //通過xorRst 來分組
first ^= a[j];
else
second ^= a[j];
}
RstState = true ;
}
return RstState ;
}
void Swap(int &a,int &b)
{
int tmp = a;
a = b;
b = tmp;
}
bool FindTreeOnceNum(int a[],int len, int &first,int &second, int &third)
{
bool RstState = false ;
if(a != NULL && len > 0)
{
int xorRst = 0,i;//異或結果
for(i=0; i<len; ++i)
xorRst ^= a[i];
//下面求 f(x^a)^f(x^b)^f(x^c)的最右邊為1的第m位
int flag =0;
for(i=0 ; i<len ; ++i)
flag ^= LastBitof1(xorRst ^ a[i]);
flag = LastBitof1(flag); //只保留最右邊為1的位
//求first
first = second = third = 0 ;
vector<int> vec;
for(i=0 ; i<len ;++i)
if(LastBitof1( xorRst ^ a[i] ) == flag )//x^a的第m位為1的情況 這裡的m位其實是最右邊為1位
first ^= a[i]; //這樣first 就求出了
for(i=0 ; i<len ;++i)
if( a[i] == first)
Swap(a[i],a[len-1]);//移除該first值
FindTwoOnceNum(a,len-1,second,third);
RstState = true;
}
return RstState ;
}
void printArray(int a[],int len)
{
if( a != NULL && len > 0)
{
for(int i=0 ; i<len; ++i)
cout<<a[i]<<" ";
cout<<"\n";
}
}
void Test1()
{
int a[] = {1,2,3,4,3,2,1};
int len = sizeof(a)/sizeof(int);
cout<<"陣列中1個出現1次的數:\n";
cout<<"陣列元素為:\n";
printArray(a,len);
cout<<"所求值為: "<<FindSingleNum(a,len)<<endl;
cout<<"---------------------------\n";
}
void Test2()
{
int a[] = {1,2,3,4,3,2,1,9};
int len = sizeof(a)/sizeof(int);
cout<<"陣列中2個出現1次的數:\n";
cout<<"陣列元素為:\n";
printArray(a,len);
int first ,second;
FindTwoOnceNum(a,len,first,second);
cout<<"所求值為: "<<first<<" "<<second<< endl;
cout<<"---------------------------\n";
}
void Test3()
{
int a[] = {1,2,3,4,3,2,1,9,7};
int len = sizeof(a)/sizeof(int);
cout<<"陣列中3個出現1次的數:\n";
cout<<"陣列元素為:\n";
printArray(a,len);
int first ,second,third;
FindTreeOnceNum(a,len,first,second,third);
cout<<"所求值為: "<<first<<" "<<second<<" " <<third<< endl;
}
int main()
{
Test1();
Test2();
Test3();
return 0 ;
}
六.參考資料
http://zhedahht.blog.163.com/blog/static/25411174201283084246412/何海濤的部落格 劍指offer作者
相關文章
- 1,2,3…n*n 的數字按照順時針螺旋的形式列印成矩陣(遞迴)矩陣遞迴
- 從陣列中找出N個數,其和為M的所有可能陣列
- 【Algorithm】《劍指offer》面試題32----從1到n整數中1出現的次數Go面試題
- 改進,從一個陣列中找出 N 個數,其和為 M 的所有可能陣列
- 求無序陣列總第n大的數陣列
- 計算陣列中每個數字出現的次數陣列
- 求一個陣列中沒有出現的最小正數陣列
- 給定一個大小為 n 的陣列,找到其中的眾數。眾數是指在陣列中出現次數大於 ⌊ n/2 ⌋ 的元素。陣列
- 從一個無序,不相等的陣列中,選取N個數,使其和為M實現演算法(javascript實現)陣列演算法JavaScript
- 洗牌演算法擴充(從n個數中隨機m個數)演算法隨機
- 陣列中每個陣列元素出現的次數陣列
- 《Cracking the Coding Interview程式設計師面試金典》----從0到n中某個數字的個數View程式設計師面試
- 【谷歌面試題】求陣列中兩個元素的最小距離谷歌面試題陣列
- 【劍指offer】陣列中只出現一次的數字(2)陣列
- 統計陣列中各數字(元素)出現的次數陣列
- 面試官:MySQL 中 varchar(n) 中 n 最大取值為多少?面試MySql
- 找出陣列中只出現一次的數字陣列
- 演算法 1~n中1的次數演算法
- 找出陣列中第 k 大的數字及其出現次數陣列
- 面試中被問到一組有序序列(從小到大),求這組序列中的前n個面試
- 每天一道演算法題:求兩個排序陣列的中位數演算法排序陣列
- 求陣列中k個數的所有組合陣列
- 求陣列中未出現的最小正整數陣列
- 建立包含N個空物件的陣列物件陣列
- Matlab 統計陣列中各數字(元素)出現的次數Matlab陣列
- 劍指 Offer 56 - I. 陣列中數字出現的次數陣列
- 面試演算法題(4)--將一個整數陣列中的所有奇數放到偶數前面面試演算法陣列
- 【.Net】從字串陣列中尋找數字的元素字串陣列
- 求1+2+...+n(Java實現)Java
- 獲取陣列第N個元素的方法陣列
- JZ-040-陣列中只出現一次的數字陣列
- 劍指offer 陣列中只出現一次的數字陣列
- 【原創】生成n*n蛇形矩陣的演算法矩陣演算法
- 陣列中重複的數字陣列
- python實現給定一個數和陣列,求陣列中兩數之和為給定的數Python陣列
- (Java) 演算法題:2的N次方Java演算法
- 統計陣列元素中每個元素出現的次數陣列
- python之矩陣相加:提示使用者輸入矩陣的行數n,再提示使用者輸入矩陣的列數m,接下來,提示使用者輸入 2*n*m 個數字(每次輸入 一個數字)。輸出 C=A+B。Python矩陣