python經典面試算法題4.1:如何找出數組中唯一的重復元素


本題目摘自《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



在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM