本題目摘自《Python程序員面試算法寶典》,我會每天做一道這本書上的題目,並分享出來,統一放在我博客內,收集在一個分類中。
【百度面試題】
難度系數:⭐⭐⭐
考察頻率:⭐⭐⭐⭐
題目描述:數字1 ~ 1000放在含有1001個元素的數組中,其中只有唯一的一個元素重復,其他數字均只出現一次。設計一個算法,將重復元素找出來,要求每個數組元素只能訪問一次。
進階:在上面題目描述中,如果不使用輔助空間,能否設計一個算法實現?
方法一:空間換時間
首先分析題目所要達到的目標
以及其中的限定條件
。從題目中可以發現,本題的目標
是在一個有且僅有一個元素重復的數組中找到這個唯一的重復元素,限定條件
是每個數組元素只能訪問一次。
在不考慮進階條件的情況下,我們可以通過字典的key
來進行去重,我們可以把數字當作key
,出現的次數記作value
:
from collections import defaultdict
def find_elem(array):
dic = defaultdict(lambda : 0) # 構造一個缺省字典,當出現KeyError的時候壓制,並創建鍵值對為key - 0
for elem in array:
if dic[elem] == 1: # 這一步如果dic中沒有elem不會報錯因為dic是defaultdict
return elem
dic[elem] += 1
return "can't find it"
print(find_elem([1, 3, 4, 2, 5, 3])) # 3
# 如果defaultdict看不懂可以看下面使用get方法的版本
def find_elem(array):
dic = {} # 使用普通字典
for elem in array:
dic[elem] = dic.get(elem, 0) + 1 # get不到elem會返回0, 然后再+1,再創建 k - v 對
if dic[elem] == 2: # 重復的元素
return elem
return "can't find it"
這種方法很容易想到,時間復雜度為O(n),空間復雜度也是O(n),這種思想在工作中可以使用,比如一個項目急着上線,沒有什么時間給你雕琢程序,那么我們可以考慮使用空間來換取程序運行的時間。
方法二:累加求和法
計算機技術與數學本身是一家,拋開計算機專業知識不提,這個問題可以回歸成一個數學問題。數學問題的目標
是在一個數字序列中尋找重復的那個數。題目描述是1 ~ 1000個數,有一個重復,那么我們把這1001個數加起來再減去 (1 + 2 + … + 1000)得到的就是重復的那個數了。
def find_it(array):
sum = 0
one_to_thousand = -1001
for i, v in enumerate(array, 1):
sum += v
one_to_thousand += i
return sum - one_to_thousand
print(find_it([i for i in range(1, 1001)] + [56]))
分析:時間復雜度O(n), 空間復雜度O(1),但是計算也挺費時間的。 其實這里可以使用python的第三方庫,科學計算庫numpy
進行並行計算,numpy.array.sum()
。
方法三:異或法
根據異或運算的性質可以直到,當相同元素異或時,運算結果為0,當相異元素異或時,運算結果非0,任何數字與數字0進行異或運算,其運算結果為該數。因為1001個數字是1 ~ 1000再加上一個大於0小於等於1000的數字,所以我們把這1001個數字和1到1000異或,最后會轉變成0和重復的那個數字進行異或,得到的就是重復的數字。
例如數組(1, 3, 4, 2, 5, 3),進行運算:(1, 3, 4, 2, 5, 3)^ (1, 2, 3, 4, 5) = 1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 3 ^ 4 ^ 4 ^ 5 ^ 5
^ 3 = 0 ^ 3 = 3。
def find_it(array):
elem = 0
for i, v in enumerate(array):
elem ^= i ^ v
return elem
print(find_it([1, 3, 4, 2, 5, 3]))
這種方法的時間復雜度是O(n), 沒有申請額外的存儲空間,進行位運算速度還算快。
方法四:數據映射法
數據的取值操作可以看作是一個特殊的函數f:D —> R
, 定義域D為下標值 0 ~ 1000
,值域為1 ~ 1000
, 如果對任意一個數 i, 把f(i)
叫做它的后繼, i叫做f(i)
的前驅。重復的那個數字有兩個前驅,所以我們可以把每個數的前驅
置為負數
,當第二次遇到重復的數字時,它的前驅已經被置為負數了,這個就是重復的數值,返回即可。
# 數據影射法
def find_it(array):
p = 0
while array[p] >= 0:
p = array[p]
array[p] *= -1
return abs(array[p])
print(find_it([1, 2, 4, 2, 5, 3])) # 2
print(find_it([1, 3, 4, 2, 5, 3])) # 3
這個算法的時間復雜度是O(n),沒有申請輔助的空間。但是這種方法修改了原本列表中的元素。
歡迎小伙伴們加入我創建的python交流群:625988679