375. Guess Number Higher or Lower II (Python)
375. Guess Number Higher or Lower II
Description
We are playing the Guess Game. The game is as follows:
I pick a number from 1 to n. You have to guess which number I picked.
Every time you guess wrong, I‘ll tell you whether the number I picked is higher or lower.
However, when you guess a particular number x, and you guess wrong, you pay $x. You win the game when you guess the number I picked.
Example:
n = 10, I pick 8. First round: You guess 5, I tell you that it‘s higher. You pay $5. Second round: You guess 7, I tell you that it‘s higher. You pay $7. Third round: You guess 9, I tell you that it‘s lower. You pay $9. Game over. 8 is the number I picked. You end up paying $5 + $7 + $9 = $21.
Given a particular n ≥ 1, find out how much money you need to have to guarantee a win.
問題描述
我們正在玩一個猜數遊戲,遊戲規則如下:
我從 1 到 n 之間選擇一個數字,你來猜我選了哪個數字。
每次你猜錯了,我都會告訴你,我選的數字比你的大了或者小了。
然而,當你猜了數字 x 並且猜錯了的時候,你需要支付金額為 x 的現金。直到你猜到我選的數字,你才算贏得了這個遊戲。
示例:
n = 10, 我選擇了8. 第一輪: 你猜我選擇的數字是5,我會告訴你,我的數字更大一些,然後你需要支付5塊。 第二輪: 你猜是7,我告訴你,我的數字更大一些,你支付7塊。 第三輪: 你猜是9,我告訴你,我的數字更小一些,你支付9塊。 遊戲結束。8 就是我選的數字。 你最終要支付 5 + 7 + 9 = 21 塊錢。
給定一個 n ≥ 1,計算你至少需要擁有多少現金才能確保你能贏得這個遊戲。
問題分析
??剛開始看到這個題,感覺是個數學問題,可以直接計算得出。寫了很久都不AC,於是又老實寫算法了。從算法角度看,這個題就是很直白的暴力破解,中間用到了DP來減小運算復雜度,還包含了取最大值和最小值運算,所以不太好分類,但是思想還是能理明白。最重要的一步DP就是i+max(?[1,...,i-1]?,?[i+1,...,n]?)。這裏的[1,...,i-1]表示猜對這個短序列最少花的錢。
??下面舉例說明。
當序列很短的時候,一眼就能看出來最少花多少錢。比如長度為2的短序列:
1 2 顯然猜1可以花錢最少,猜對不花錢,錯了花1塊,所以最多需要1塊錢。
長度為3的短序列同理。比如:2 3 4
對於這種短序列,我們很快可以得到答案,直接每個數試一試就能知道結果。具體這樣試,- 如果先猜2,錯了再猜3(知道3比4花錢少),那麽5塊錢可以保證一定猜對。
- 如果先猜3,不管對錯,肯定能得到正確答案,那麽只需3塊錢。
- 如果先猜4,錯了再猜2(知道2比3花錢少),那麽需要6塊錢。
- 綜上,選最好的猜法,最少只要3塊錢即可。
這其實是我們內心的真實邏輯,寫成代碼就簡單了,如果序列是[ i , i+1 ]和[ i-1 , i , i+1 ]這樣的,直接猜i就好了。
對於中長序列,比如1到4的序列怎麽處理呢,其實邏輯一樣:
1 2 3 4
如果沒有策略,純暴力搜索把所有猜法都試一遍,復雜度是指數級的。於是我們可以在暴力搜索的基礎上將任務分解成一個個小任務,即用DP的思路,創建一個二維表格,記住小任務的解,最終得到大任務的解。
比如前面[ 2 , 3 , 4 ]的例子裏思路也是一樣的。猜2不對,我們知道猜3而不是4,就是因為我們心裏默認知道[ 3 , 4 ]這個小任務只需要猜3就夠了,即[ 3 , 4 ]這個小任務只需要3塊錢而不是4塊錢。
這裏也是同理:先猜1
1 2 3 4 先猜2
1 2 3 4
這裏有個取最大的操作,原因是為了把所有情況都考慮進去,這樣無論是哪一種情況,錢都是夠的。先猜3
1 2 3 4 先猜4
1 2 3 4
那麽最終至少要花的錢就是min(先猜1,先猜2,先猜3,先猜4)=min(4,5,4,6)=4 。
這裏有個取最小值的操作,就是在所有暴力破解的方法中,找花錢最少的那種方法,這樣就能用最少的錢猜到正確數字。
另外可以看出,我們每次都把任務分解成左右兩個小任務,只要知道了小任務的最優值,就能求得大任務的最優值了。
再舉一個長序列的例子說明任務分解,比如1到10:
1 2 3 4 5 6 7 8 9 10 for i in range(1,11)
從頭試到尾,這樣把任務分成了兩個小任務,即[1,...,i-1]和[i+1,...,10],然後只要我們知道每個小任務的解,那麽先猜i的這個大任務的解就是i+max([1,...,i-1],[i+1,...,10])。比如我們先猜了6
1 2 3 4 5 6 7 8 9 10
所有1到10都試過之後,再取最小值,得到的就是花費最少的錢數了。即min( 先猜1花的錢 , ... , 先猜10花的錢 )。
最後的問題是怎麽構造二維數組存儲小任務的解呢,也很簡單。這裏以1到6舉例,太長表格不好畫。
1 2 3 4 5 6 表格建立如下,記為T:
1 2 3 4 5 6 1 2 3 4 5 6
註意:這裏我直接從1開始計數便於解釋說明,代碼的計數是從0開始的。首先初始化表格為0
1 2 3 4 5 6 1 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0 0 0 0 4 0 0 0 0 0 0 5 0 0 0 0 0 0 6 0 0 0 0 0 0 任務終點為1的時候,此時序列為[ 1 ],不用花錢就能猜對,則有T[1][1]=0。
終點為1填寫完畢。此時T沒變1 2 3 4 5 6 1 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0 0 0 0 4 0 0 0 0 0 0 5 0 0 0 0 0 0 6 0 0 0 0 0 0 任務終點為2的時候,
任務起點先設為2,此時序列為[ 2 ],不用花錢,則有T[2][2]=0。
下一步任務起點設為1,此時序列為[ 1 , 2 ],花1即可,則有T[2][1]=1。
終點為2填寫完畢,此時T為1 2 3 4 5 6 1 0 0 0 0 0 0 2 1 0 0 0 0 0 3 0 0 0 0 0 0 4 0 0 0 0 0 0 5 0 0 0 0 0 0 6 0 0 0 0 0 0 任務終點為3的時候,
任務起點先設為3,此時序列為[ 3 ],不用花錢,則有T[3][3]=0。
任務起點設為2,此時序列為[ 2 , 3 ],花錢2,則有T[3][2]=2。
任務起點設為1,此時序列為[ 1 , 2 ,3 ],三個的短序列直接可得花錢為中間那個數,也就是2,則有T[3][1]=2。
終點為3填寫完畢,此時T為1 2 3 4 5 6 1 0 0 0 0 0 0 2 1 0 0 0 0 0 3 2 2 0 0 0 0 4 0 0 0 0 0 0 5 0 0 0 0 0 0 6 0 0 0 0 0 0 任務終點為4的時候,
任務起點設為4,此時序列為[ 4 ],不花錢,T[4][4]=0。
任務起點設為3,此時序列為[ 3 , 4 ],花錢3,則有T[4][3]=3。
任務起點設為2,此時序列為[ 2 , 3 , 4 ],花錢3,則有T[4][2]=3。
任務起點設為1,此時序列為[ 1 , 2 , 3 , 4 ],長度超過3了,要用中長序列的方式去計算。也就是前面舉例的方式,有T[4][1]=min(先猜1花的錢,...,先猜4花的錢)
\[ \begin{array}{l} =min(1+[2,3,4] \ , \ 2+max([1],[3,4]) \ , \ 3+max([1,2],[4] \ , \ 4+[1,2,3])) \=min(1+T[4][2] \ , \ 2+max(T[1][1],T[4][3]) \ , \ 3+max(T[2][1],T[4][4]) \ , \ 4+T[3][1]) \=min(1+3 \ , \ 2+3 \ , \ 3+1 \ , \ 4+2) \=4 \end{array} \]
即T[4][1]=4。
終點為4填寫完畢,此時T為1 2 3 4 5 6 1 0 0 0 0 0 0 2 1 0 0 0 0 0 3 2 2 0 0 0 0 4 4 3 3 0 0 0 5 0 0 0 0 0 0 6 0 0 0 0 0 0 - 任務終點為5的時候,
任務起點設為5,此時序列為[ 5 ],不花錢,T[5][5]=0。
任務起點設為4,此時序列為[ 4 , 5],花錢4,則有T[5][4]=4。
任務起點設為3,此時序列為[ 3 , 4 ,5 ],花錢4,則有T[5][3]=4。
任務起點設為2,此時序列為[ 2 , 3 , 4 ,5],有
\[ \begin{array}{l} T[5][2] \=min(2+T[5][3],3+max(T[2][2],T[5][4]),4+max(T[3][2],T[5][5])) \=min(2+4,3+4,4+2) \=6 \end{array} \]
任務起點設為1,此時序列為[ 1 , 2 , 3 , 4 , 5 ],有
\[ \begin{array}{l} T[5][1] \=min(1+T[5][2],2+max(T[1][1],T[5][3]),3+max(T[2][1],T[5][4]),4+max(T[3][1],T[5][5]),5+T[4][1]) \=min(1+6,2+4,3+4,4+2,5+4) \=6 \end{array} \] 終點為5填寫完畢,此時T為
1 2 3 4 5 6 1 0 0 0 0 0 0 2 1 0 0 0 0 0 3 2 2 0 0 0 0 4 4 3 3 0 0 0 5 6 6 4 4 0 0 6 0 0 0 0 0 0 任務終點為6的時候,
任務起點設為6,此時序列為[ 6 ],不花錢,T[6][6]=0。
任務起點設為5,此時序列為[ 5 , 6 ],花錢5,則有T[6][5]=5。
任務起點設為4,此時序列為[ 4 , 5 , 6 ],花錢5,則有T[6][4]=5。
任務起點設為3,此時序列為[ 3 , 4 , 5 , 6 ],有
\[ \begin{array}{l} T[6][3] \=min(3+T[6][4],4+max(T[3][3],T[6][5]),5+max(T[4][3],T[6][6]),6+T[5][3]) \=min(3+5,4+5,5+3,6+4) \=8 \end{array} \]
任務起點設為2,此時序列為[ 2 , 3 , 4 , 5 , 6 ],有
\[ \begin{array}{l} T[6][2] \=min(2+T[6][3],3+max(T[2][2],T[6][4]),4+max(T[3][2],T[6][5]),5+max(T[4][2],T[6][6]),6+T[5][2]) \=min(2+8,3+5,4+5,5+3,6+6) \=8 \end{array} \]
任務起點設為1,此時序列為[ 1 , 2 , 3 , 4 , 5 , 6 ],有
\[ \begin{array}{l} T[6][1] \=min(1+T[6][2],2+max(T[1][1],T[6][3]),3+max(T[2][1],T[6][4]),4+max(T[3][1],T[6][5]),5+max(T[4][1],T[6][6]),6+T[5][1]) \=min(1+8,2+8,3+5,4+5,5+4,6+6) \=8 \end{array} \]
終點為6填寫完畢,此時整個表格填寫完畢。
有T為1 2 3 4 5 6 1 0 0 0 0 0 0 2 1 0 0 0 0 0 3 2 2 0 0 0 0 4 4 3 3 0 0 0 5 6 6 4 4 0 0 6 8 8 8 5 5 0 到此,T[6][1]=8即為結果。再把上述思路轉為代碼就搞定了。
代碼
class Solution:
def getMoneyAmount(self, n):
"""
:type n: int
:rtype: int
"""
L = [[0]*(n+1) for i in range(n+1)]
for i in range(1,n+1):
for j in range(1,i)[::-1]:
M = n*n
if i-j<=2:
M = i-1
else:
for k in range(j+1,i):
M = min(M,k+max(L[k-1][j],L[i][k+1]))
L[i][j] = M
return L[n][1]
測試結果
- Runtime:692 ms
- beats 69.5% of python3 submissions
375. Guess Number Higher or Lower II (Python)