【程式設計的樂趣-用python解演算法謎題系列】謎題一 保持一致
謎題一 保持一致
謎題
假設有一大群人排隊等待觀看棒球比賽。他們都是主場球迷,每個人都戴著隊帽,但不是所有人都用同一種戴法,有些人正著戴,有些人反著戴。
假定你是保安,只有在全組球迷帽子戴法一致時才能讓他們進入球場,要麼全部正著戴,要麼全部反著戴。因為每個人對正戴和反戴的定義並不相同。因此你不能對他們說把帽子正著戴或反著戴,只能告訴他們轉一下帽子。
舉個栗子(我們用 F 表示正戴,B 表示反戴)
F F B B B F B B B F F B F
上面是一個13人的隊伍,位置從0 ~ 12。你可以發出以下指令:
請 0 號位置的人轉一下帽子,
請 1 號位置的人轉一下帽子,
......
然後分別對5,9,10,12號位置的人發出同樣的指令,總共要發出六次指令。
我們也可以發出這樣的指令:
請 2~4 號位置的人轉一下帽子,
請 6~8號位置的人轉一下帽子,
請 11 號位置的人轉一下帽子。
只需要 3 次指令。
我們的要求是讓保安生成的命令數最少。難度更大的問題是:能否第一次沿著隊伍就得正確答案呢?
演算法
演算法一 尋找想法相同的連續人員
計算正戴區間和反戴區間的個數, 區間數更少的即是我們要反轉的帽子區間。
def pleaseconform(caps): section = [] # 統計各區間的列表 start = 0 Fnum = 0 # 正戴區間數 Bnum = 0 # 反戴區間數 for i in range(1,len(caps)): if caps[start] != caps[i]: # 標誌著新區間的產生 section.append([start , i-1 ,caps[start]]) if caps[start]=='F': Fnum += 1 else: Bnum += 1 start = i section.append([start,len(caps)-1,caps[start]]) # 6~13行程式碼未新增最後一個區間,這行程式碼用於新增最後一個區間 if caps[start]=='F': Fnum += 1 else: Bnum +=1 if Fnum>Bnum : flag = 'B' else: flag = 'F' for t in section: if t[2]==flag: if(t[0] == t[1]): print("請"+str(t[0])+"號位置的人反轉帽子") else: print("請"+str(t[0])+"到"+str(t[1])+"的人反轉帽子") caps = ['F','F','B','B','B','F','B','B','B','F','F','B','F'] pleaseconform(caps) """ Output: 請2到4的人反轉帽子 請6到8的人反轉帽子 請11號位置的人反轉帽子 """
- 我們注意到演算法一的核心程式碼 6~13 行未新增最後一個區間,因此我們要再新增程式碼來完善演算法,顯得過於繁瑣。實際上,我們只需要在 caps 列表新增一個其他元素如'M',就可以消除掉這種情況。
# 程式碼優化 def pleaseconform(caps): caps.append('M') section = [] start = 0 Fnum = 0 Bnum = 0 for i in range(1,len(caps)): if caps[start] != caps[i]: section.append([start , i-1 ,caps[start]]) if caps[start]=='F': Fnum += 1 else: Bnum += 1 start = i if Fnum>Bnum : flag = 'B' else: flag = 'F' for t in section: if t[2]==flag: if(t[0] == t[1]): print("請"+str(t[0])+"號位置的人反轉帽子") else: print("請"+str(t[0])+"到"+str(t[1])+"的人反轉帽子") caps = ['F','F','B','B','B','F','B','B','B','F','F','B','F'] pleaseconform(caps) """ Output: 請2到4的人反轉帽子 請6到8的人反轉帽子 請11號位置的人反轉帽子 """
演算法二 單遍演算法one pass
通過觀察,實際上我們只需要通過 caps 列表中第一隻帽子的方向,就可以得出我們需要反轉的是正戴區間還是反戴區間。因為第一隻帽子方向區間的個數一定大於等於另一方向的區間數。基於這一觀察,能夠實現一個one pass 演算法。
# one pass
def pleaseconformonepass(caps):
caps.append(caps[0])
for i in range(1,len(caps)):
if(caps[i] != caps[i-1]):
if(caps[i] != caps[0]):
print("請"+str(i)+"號位置到")
else:
print(str(i-1)+"號位置的人反轉帽子")
pleaseconformonepass(caps)
"""
Output:
請2號位置到
4號位置的人反轉帽子
請6號位置到
8號位置的人反轉帽子
請11號位置到
11號位置的人反轉帽子
"""
謎題背後
這道謎題背後的出發點是資料壓縮。向同一方向的人發出的命令資訊是相同的,可以被壓縮為一組較少的命令,其中每一條命令指揮一組連續的人。
謎題拓展
資料壓縮有多種實現方式,在思路上接近於這道習題的一種演算法叫做遊程編碼。舉一個最簡單的例子最容易描述,假設有以下字串:
WWWWWWWWWWWWWBBWWWWWWWWWWWWBBBBB
使用遊程編碼演算法,我們可以把上述字串壓縮為一個由數字和字元構成的字串:
13W2B12W5B
遊程解碼演算法就是把' 13W2B12W5B '解壓為原始字串的過程。
現代計算機的壓縮工具,便是利用了這種思想相關的演算法。
以下是遊程編碼解碼的具體實現:
def youcengbianma(string):
start=0
newstring = ''
for i in range(1,len(string)):
if(string[start]!=string[i]):
newstring += str(i-start)
newstring += string[start]
start=i
newstring += str(i-start+1)
newstring += string[start]
return newstring
print(youcengbianma("wwweeewwwweeffeee"))
"""
Output:
3w3e4w2e2f3e
"""
def youcengjiema(string):
num = ''
newstring = ''
for i in range(0,len(string)):
if(not string[i].isalpha()): # isalpha() 如果是字母字元,返回true
num += string[i]
else:
for j in range(0,int(num)):
newstring += string[i]
num = ''
return newstring
print(youcengjiema("3w3e4w2e2f3e")
"""
Output:
wwweeewwwweeffeee
"""