【編程的樂趣-用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
"""


免責聲明!

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



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