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))

 

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