演算法 舉一反三之n重複陣列中找唯一m重複異類數

2022-08-05 12:39:12 字數 4026 閱讀 8768

n重複陣列,是指陣列中的數字都出現n次;

唯一m重複異類數,是指存在唯一一個沒出現n次,只出現了m次的數;

這裡我簡記它為nx+my問題,求解y,其中m < n,陣列中都是整數;

一直沒有精力刷leetcode,今天查問題無意中看到了leetcode 137:給定一個非空整數陣列,除了某個元素只出現一次以外,其餘每個元素均出現了三次;找出那個只出現了一次的元素。要求時間複雜度o(n),空間複雜度o(1);

沒有在第一時間想到思路,於是花時間研究了一下;

這是一個3x+y的問題;先給出最優解:

a, b =0, 0

for num in

nums:

a = (a ^ num) & ~b

b = (b ^ num) & ~a

return a

在2x+y問題中,方法是使用異或操作一遍全陣列,最終結果就是那個異類數,但從未深入想過為什麼異或會對2x+y問題有效,因而也就無法想通為什麼直接異或會對3x+y問題無效;

再看一遍異或的特點:

0 ^ 0 =0

1 ^ 0 = 10 ^ 1 = 1

1 ^ 1 = 0

仔細看看,這是因為異或可以讓一個資訊位(前面的那個1bit數),在接收到有效資訊量(後面的那個1bit數,值為1時為有效)的時候,產生二態變化;

因而如果要解決3x+y問題,就需要有一個可以產生三態變化的操作符,單純的異或是肯定不行的,因為要對一個資訊位儲存三種狀態,至少需要兩個資訊位,因而在原本的數字變數空間上操作是不夠的;

假定這個三態異或操作用符號^

3表示;然後用兩個資訊位(b, a)儲存狀態,於是三態異或在接收到有效資訊量(1)時的運算特點如下(接收到無效資訊量0時不產生變化):

(0, 0)  ^3  1 =  (0, 1)

(0, 1) ^3 1 = (1, 0)

(1, 0) ^3 1 = (0, 0)

這個三態變化過程,其實是正常的兩位二進位制數進位加法的一個修改,修改處在於,對於二進位制10,再加1時,直接跳過11回到00;

因而這個狀態變化過程可描述為:

兩位二進位制數的低位a,在高位b為0時,進行二態變化;在高位b為1時,歸0;

兩位二進位制數的高位b,在低位a歸0時,進行二態變化;在低位a變為1時,保持為0;

可見a與b的變化規律相似,都要求對方為0時自己進行二態變化,對方為1時自己為0,於是上面的最優解的計算過程就很容易寫出來了:

a = (a ^ num) & ~b

b = (b ^ num) & ~a

等等,為什麼是return a,而不是return b呢?

首先,上面這段**裡的a、b各是一個數字,它位的每個二進位制位組合起來,儲存了對整個陣列^

3操作過程中每個二進位制位的狀態變化;

因為這個^

3操作過程中,低位a在第一狀態有效,因而要求3x+y問題,需要return a;

因而,如果題目改為:給定一個非空整數陣列,除了某個元素只出現兩次以外,其餘每個元素均出現了三次;找出那個只出現了兩次的元素。

求解這個3x+2y問題,答案也就出來了,計算過程同上,而因為高位b在第二狀態有效,因而return b即可;

再發散思維,改題目:給定一個非空整數陣列,除了某個元素只出現兩次以外,其餘每個元素均出現了四次;找出那個只出現了兩次的元素。

沿著上面的思路,需要做一個四態異或操作符^4,同樣需要兩個資訊位進行狀態儲存,它對於有效資訊(1)的運算特點如下:

(0, 0) ^4  1 = (0, 1)

(0, 1) ^4 1 = (1, 0)

(1, 0) ^4 1 = (1, 1)

(1, 1) ^4 1 = (0, 0)

這就是標準的兩位二進位制數加法,狀態變化過程可描述為:

兩位二進位制數的低位a,正常進行二態變化;

兩位二進位制數的高位b,在低位a進位的時候進位二態變化;

於是計算過程可寫為:

old_a =a

a = (a ^num)

b = b ^ ((old_a ^ a) & ~a)

其中(old_a ^ a) & ~a,意義為a由1變為0;

由於這個b的計算核心運算元是異或,異或與否合用有危險,不把(old_a ^ a)引進來會出現bug;

因為要求的y出現2次,所以需要找第二狀態時的有效位,return b;

狀態數同時為4,計算過程同4x+2y,返回時找第三狀態時的有效位,因而也return b;

題目修改為:給定一個非空整數陣列,除了某個元素只出現4次以外,其餘每個元素均出現了5次;找出那個只出現了4次的元素。

狀態數上升到5,需要做一個五態異或操作符^5,需要3個資訊位(c, b, a)做狀態儲存,對有效資訊(1)的運算特點如下:

(0, 0, 0) ^5  1 = (0, 0, 1)

(0, 0, 1) ^5 1 = (0, 1, 0)

(0, 1, 0) ^5 1 = (0, 1, 1)

(0, 1, 1) ^5 1 = (1, 0, 0)

(1, 0, 0) ^5 1 = (0, 0, 0)

狀態變化過程可描述為:

三位二進位制數的低位a,在高位c為0時,進行二態變化;在高位c為1時,歸0;

三位二進位制數的中位b,在高位c為0時,低位a進位的時候進位二態變化;在高位c為1時,歸0;

三位二進位制數的高位c,在低位a和中位b同時歸0時,進行二態變化;

計算過程可寫為:

old_a =a

a = (a ^ num) & ~c

b = (b ^ ((old_a ^ a) & ~a)) & ~c

c = (c ^ num) & ~b & ~a

由於y出現的次數是4,高位c在狀態4時有效,因而返回c;

測試**:

def

x3_y1(nums):

a, b =0, 0

for num in

nums:

a = (a ^ num) & ~b

b = (b ^ num) & ~a

return

adef

x3_y2(nums):

a, b =0, 0

for num in

nums:

a = (a ^ num) & ~b

b = (b ^ num) & ~a

return

bdef

x4_y2(nums):

a, b =0, 0

for num in

nums:

old_a =a

a = (a ^num)

b = b ^ ((old_a ^ a) & ~a)

return

bdef

x4_y3(nums):

return

x4_y2(nums)

defx5_y4(nums):

a, b, c =0, 0, 0

for num in

nums:

old_a =a

a = (a ^ num) & ~c

b = (b ^ ((old_a ^ a) & ~a)) & ~c

c = (c ^ num) & ~b & ~a

return

cprint (x3_y1([2, 3, 2, 2])) # 3

print (x3_y2([2, 3, 2, 3, 2])) # 3

print (x4_y2([2, 2, 3, 2, 3, 2])) # 3

print (x4_y3([2, 2, 3, 2, 3, 3, 4, 3, 4, 4, 2])) #4

print (x4_y3([5, 5, 8, 5, 8, 8, 5])) # 8

print (x5_y4([5, 5, 8, 5, 8, 8, 5, 8, 5])) # 8

執行結果:

333

488