1. 程式人生 > >【程式設計的樂趣-用python解演算法謎題系列】謎題一 保持一致

【程式設計的樂趣-用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
"""