昨天 下午朋友發了我一道LeetCode面試題:
給定一個沒有重復的數字序列,返回其所有可能的全排列。示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
我一看,這很簡單啊,而且題目要求也很清晰,就打算五分鐘之內寫完發給他。
然而寫了二十分鐘,居然,沒有,寫,出,來!
我好菜啊…… (果然算法題是我的噩夢)
網上看了一圈,發現別人的解法涉及到交換位置,講的也不太清楚的樣子。下班在地鐵上,我就開動了并不聰明的腦筋,好好想了一下。
明確問題
全排列是一道高中數學必備的排列組合問題。我們知道:對一個去重列表的所有的可能情況,是 n!
假設列表有六個元素,那么第一個元素的選擇可能有六種情況,第二個有剩下的五種,第三個有四種……然后依次相乘得到所有的情況數。
或者從相反方向來建立,如果列表中只有一個數,那么只有唯一一種排列方式;
如果有兩個數字ab,那么就有a,b 和b,a兩種排列方式;
這個時候,可以認為是結果的第一位有兩種選擇:選a或者b,剩下的一位就是上面的一種排列方式。
這么看可能還不夠直觀,接下來我們看abc三個數字的情況:
第一位有a,b,c三種可能性,我們如果挑出了a放在結果第一位上,剩下的后兩位就是b,c的組合結果,由上述兩種數字排列可知,此時二,三位可以是b,c或者c,b排列,這就是兩種情況;接下來把第一位換成b,剩下兩位就是a,c或者c,a排列;選擇c同理……
如果有abcd四個數字,那么第一位可以選擇abcd任意一個,剩下三位就是剩下三個數字在上一步的全排列。
實際上,我們全排列,就是在n-1的那一層排列情況上再構建了一層,第一位的數字永遠有n種可能性,剩下的幾位就是上一步推出的n-1層排列組合結果,填進去就好了。
需要用到遞歸解法。遞歸的終點是:在只剩一個數字的情況下返回這個數字,列表為空返回空。
代碼如下:
import copy
def permuation(x: list):
result = []
if not x:
return
if len(x) == 1:
return [x]
for num in x:
new_list = copy.copy(x)
new_list.remove(num)
for l in permuation(new_list):
if l:
res = [num]
res.extend(l)
result.append(res)
return result
print(permutation([1,2,3]))
>> [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
python由于自身特性,對于列表list的操作時一定要牢記:list在python中是可變對象,意味著如果想要給新列表賦值,需要用到copy來實現復制已有列表的功能,否則用簡單的a = b就會把同一個列表指向a,這是我們不想看到的。
此外,列表的添加項append,擴展extend,刪除項remove 等操作,都是在原列表上進行的,所以不能用賦值,否則會返回空None。而且,代碼里的這一段:
res = [num]
res.extend(l)
result.append(res)
如果想要(偷懶)簡化成:
result.append([num].extend(l))
也會得到None,所以需要先創建結果子列表再添加。