House Robbing算法分析-Python實現

問題描述

Money robbing
A robber is planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
1. Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
2. What if all houses are arranged in a circle?

問題分析

這個問題與揹包問題類似,但加了限制條件:兩個相鄰的房屋不能一晚同時被搶,且沒有最大容量限制。現在假設我們已經得到子問題的最優解,當前考慮第一個決策即考慮最後一個房子搶還是不搶。如果搶的話,則原問題的解變成了最後一個房子的錢加上n-2個房子的錢;如果不搶的話,則原問題的解變成了最後n-1個房子的錢。這兩種選擇的最大值就是最終的解。可以得到遞歸式:

當n=0、1、2時,情況分別是沒有房子、只有一個房子、有兩個房子。沒有房子時,也沒有錢財可以拿到,返回0。只有一個房子時,最大的收益就是搶了這個房子的錢財。有兩個房子時,因爲無法同時搶劫鄰接的兩個房子,所以最大的收益是搶擁有最大錢財的其中一個房子。

當n=3時,搶劫只存在兩種選擇:搶1和3,或只搶2,並取這兩種選擇的最大值。這也可以看成是考慮第3個房子搶不搶,如果搶的話,可以繼續搶1號房子;如果不搶的話,就只能搶2號房子。所以得到的收益就是1號房子與3號房子錢財之和、2號房子的錢財,兩者取最大值。

對於第i個房子,執行遞推表達式:sum[i] = max(sum[i-1], sum[i-2]+house[i]),也就是將第i個房子分爲兩個選擇,如果不搶的話,獲得的錢就是sum[i-1];如果搶的話,就是sum[i-2]+house[i]。每種選擇對應一個收益,取兩者中的最大收益。

Python代碼

def Robbing(house):
    OPT = [0] * len(house)  #初始化數組
    if len(house)==0:  #沒有錢可以拿到
        return 0
    if len(house)==1:  #只有一個房子時,返回這個錢財即爲最大
        #OPT[0] = house[0]
        return OPT[0]
    if len(house)==2:  #有兩個房子時,因爲不能搶相鄰的,所以取最大錢財即可
        #OPT[1] = max(house[0], house[1])
        return max(house[0], house[1])
    OPT[0] = house[0]
    OPT[1] = max(house[0], house[1])
    for i in range(2, len(house)):  #多於3個房子時
        OPT[i] = max(OPT[i-1], OPT[i-2]+house[i]);  #//把複雜的問題分爲第i個房子的錢搶不搶,如果不搶的話,獲得的錢就是sum[i-1];如果搶的話,就是sum[i-2]+house[i]。取這兩者最大值即可
    return OPT[len(house)-1]

if __name__ == "__main__":
    n = int(input().strip())  #n代表房子總數
    house = [int(x) for x in input().split()]  #house代表每個房子對應的錢財
    print(Robbing(house))
    

當所有的房子圍成一個圈的結構時

此時還是類似的思路,需要間隔1個房子搶錢財。但因爲出現圈的結構,第一個房子和最後一個房子是相鄰的,則若搶了第1個房子就不能搶最後一個房子,若搶了最後一個房子就不能搶第一個房子。假設兩個指針i、j分別指向房子的起始和末尾,所以此時分解子問題應該考慮:對於第i個房子,如果搶第i個房子,則子問題變成第i+2個房子和第j-1個房子之間的問題;如果不搶第i個房子,則子問題變成第i+1個房子和第j個房子之間的問題。可得到遞歸表達式爲:

或者直接利用非圈結構時的遞歸表達式:做兩次動態規劃,即:,也就是在搶與不搶第一個房子的兩種情況中取最大錢財。

Python代碼

def Robbing_circle(house, n):
    OPT1 = [0] * n
    OPT2 = [0] * n
    if n==0:
        return 0
    if n==1:
        return house[0]
    if n==2:
        return max(house[0], house[1])
    OPT1[0], OPT1[1] = house[0], house[0]  #搶第一個房子,不搶最後一個房子
    OPT2[0], OPT2[1] = 0, house[1]  #不搶第一個房子,搶最後一個房子
    for i in range(2, n):  #兩次動態規劃,最終取兩種情況的最大值
        OPT1[i] = max(OPT1[i-1], OPT1[i-2]+house[i]) 
        OPT2[i] = max(OPT2[i-1], OPT2[i-2]+house[i])
    return max(OPT1[n-2], OPT2[n-1])  #注意返回OPT1和OPT2的下標不同
if __name__ == "__main__":
    n = int(input().strip())  #n代表房子總數
    house = [int(x) for x in input().split()]  #house代表每個房子對應的錢財
    print(Robbing_circle(house, n))

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章