全排列
LeetCode 46. 全排列
給定一個沒有重複數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
方法1:使用庫函數itertools.permutations
Python 中文文檔3.7.2rc1itertools
# 方法一
'''
輸入:
1 2 3
輸出:
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
'''
import itertools
n = list(map(int, input().split()))
list = list(itertools.permutations(n))
for i in range(len(list)):
print(list[i])
方法二:回溯
nums = [1,2,3]
res = []
def backtrack(nums, tmp):
if not nums:
res.append(tmp)
return
for i in range(len(nums)):
backtrack(nums[:i] + nums[i+1:], tmp + [nums[i]])
backtrack(nums, [])
print(res)
回溯算法框架:
解決一個回溯問題,實際上就是一個決策樹的遍歷過程。你只需要思考 3 個問題:
1、路徑:也就是已經做出的選擇。
2、選擇列表:也就是你當前可以做的選擇。
3、結束條件:也就是到達決策樹底層,無法再做選擇的條件。
代碼方面,回溯算法的框架:
result = []
def backtrack(路徑, 選擇列表):
if 滿足結束條件:
result.add(路徑)
return
for 選擇 in 選擇列表:
做選擇
backtrack(路徑, 選擇列表)
撤銷選擇
其核心就是 for 循環裏面的遞歸,在遞歸調用之前「做選擇」,在遞歸調用之後「撤銷選擇」
二進制枚舉
定義
先給出子集的定義:子集是一個數學概念:如果集合A的任意一個元素都是集合B的元素,那麼集合A稱爲集合B的子集。
用途
在寫程序的時候,有時候我們可能需要暴力枚舉出所有的情況,這時可以考慮通過二進制來枚舉子集來嘗試解決問題。
解釋
假設我們現在有5個小球,上面分別標號了0,1,2,3,4代表這些小球的權值,現在要像你求出這些小球的權值可以組成的所有情況。
假設我們現在有5個小球,上面分別標號了0,1,2,3,4代表這些小球的權值,現在要像你求出這些小球的權值可以組成的所有情況。
我們用二進制的思維來考慮這個問題,因爲有5個小球,所以我們用5個比特位來分別標記小球存在還是不存在,對於這樣一種情況,比如我們現在要選擇3個小球,分別是0,3,4號小球,那麼我們用二進制1表是當前的小球存在,用0表示當前小球不存在
二進制下標 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
二進制 | 1 | 1 | 0 | 0 | 1 |
小球狀態 | 存在 | 存在 | 不存在 | 不存在 | 存在 |
我們可以用5個比特位來表示這種情況,如果小球全部選擇的話那麼二進制表示就是11111
,二進制的11111
轉化爲十進制數字就是31,這個數字正好就是2^5 -1
,那麼我們可以用從0~(2^5−1)
這些數表示完所有的選取狀態(因爲這個範圍內的二進制數情況正好包括了這些選取狀況).
所以我們遍歷每一個集合:
for i in range(1<<n):
設s = 1(二進制爲00001)代表我們選0位置上的數值;
那麼我們如何找到每個位置上的數值呢?
我們遍歷的是二進制的十進制表示,我們當然可以轉化爲二進制再枚舉每一位,但是,這很麻煩;
一個很巧妙的方式就是利用位運算。
1<<0=1(0);
1<<1=2(10);
1<<2=4(100);
1<<3=8(1000);
1<<4=16(10000);
...
1<<7=128(10000000);
...
看出來了吧!我們只需要將n&(1<<i)我們便可以得到每一位是不是1 (1<< i 除了那一位,剩餘的都是0,所以我們就可以得到那一位是不是1)
按位與運算符(&)
參加運算的兩個數據,按二進制位進行“與”運算。
運算規則:0&0=0; 0&1=0; 1&0=0; 1&1=1;
即:兩位同時爲“1”,結果才爲“1”,否則爲0
例如:3&5 即 0000 0011& 0000 0101 = 00000001 因此,3&5的值得1。
左移運算(<<)
a << b就表示把a轉爲二進制後左移b位(在後面添b個0)。例如100的二進制爲1100100,而110010000轉成十進制是400,那麼100 << 2 = 400。
可以看出,a << b的值實際上就是a乘以2的b次方,因爲在二進制數後添一個0就相當於該數乘以2(這樣做要求保證高位的1不被移出)。
題目舉例:
憂鬱的JM,借酒消愁。略微喝醉的他,和下酒花生聊起了天。
JM:“你知道質數是什麼嗎?”
花生:“…”
JM:“質數是指在大於11的自然數中,除了11和它本身以外不再有其他因數的自然數。”
花生:“…”
JM:“現在我有一個質數集合{3, 5, 7, 11, 13, 19, 23, 29, 31, 37, 41, 53, 59, 61, 67, 71, 97, 101, 127, 197, 211, 431}3,5,7,11,13,19,23,29,31,37,41,53,59,61,67,71,97,101,127,197,211,431,你可以從中挑出任意多個(0-12個)不同的數出來構成一個新數(取出數的和)”
JM:“構成的新數從小到大依次爲:0, 3, 5, 7, 8, 10, 11, 12, 13…,0,3,5,7,8,10,11,12,13…,你知道[0, 1694][0,1694]中有多少個數是沒法構成的嗎?”
花生:”…“
JM:“例如:1,2,4…1,2,4…均是不能夠從質數集合中挑數構成”
你來幫幫花生吧~
思路:
總共 22 個數,選擇其中的 0 -12 個數,加上來組成一個新數。
我們可以用二進制枚舉,對於 22 個數,每一個數,只有拿或不拿兩種情況,也就是 0 或者 1。所以總共有 2 ^ 22 約等於 4e6。不會超時。
因爲我們用二進制枚舉,每一位對應這個數要不要取,如果取,那就累和。還要注意,最後只能取 12 個,所以我們要判斷,這種取法中 1 的個數,如果是 >12 ,那這種方案不成立。
然後算出所有情況的數,用 set 統計(可能有重複的,去重)。
最後答案是問,無法構成的個數,因此答案是 : 總數(1695) - set 中的數(可以構成了這麼多數)
具體可以參考我的這篇博客:Python算法學習:全排列的回溯實現與二進制枚舉
nums = [3,5,7,11,13,19,23,29,31,37,41,53,59,61,67,71,97,101,127,197,211,431]
ans = [] #存放所有答案
for i in range(1<<22): # 2^22-1種情況(這裏是取0或者取22個數的全部可能情況)
cnt = 0 # 計數 控制取數不超過12
res = 0 # 結果
tmp = i
for j in range(22): # 查看22位中都有哪一位放了數字,即是1
if (tmp >> j) & 1: # 如果第j位是1,則符合
cnt += 1
res += nums[j]
if cnt <= 12: # 不超過12位
ans.append(res)
ans = set(ans) # 使用集合的特性,去重
cnt = 1695
print(ans)
print(cnt - len(ans))