一、問題
圖中的8個灰色在柱狀分別表示八份工作。所跨的寬度為做完該工作需要花費的時間。柱體中的紅色手寫數字為做完該份工作所能掙到的錢。
假設我們不能同時做有時間沖突的工作,問在0-11這個時間范圍內,我們最多能掙多少錢?如何安排做哪些工作賺的錢最多。
二、分析問題
這是一個典型的可以使用動態規划(DP)來解決的問題。
1.找出遞歸規律
首先,我們假設我們一定要做第8份工作的時候,由於一定要做第8份工作,那么由於第6、7份工作與第8份工作有時間沖突,所以6、7不能做。
那么,我們在決定是否選擇做第8份工作的時候,假設最大值為OPT(8)。
所以得到以下公式:
1)如果選擇做第8份工作,則OPT(8) = val(8) + OPT(5) (因為一定要做8的話,前面最多能做到第5份工作)
2)如果不選擇做第8份工作,則OPT(8) = OPT(7) (OPT(7)表示是否選擇做7的最大收入)
3)我們在選與不選之間取他們的最大值,最終得到真正的OPT(8)
2.寫出遞歸式
公式中的prev(i)就是一定要做第i份工作時,前面最近能做的某份工作的編號。例如prev(8) = 5。
prev是我們能夠直接通過題目計算出來的:
有了prev表,我們的OPT就能夠全部算出來了。
3.拆分計算過程
從圖中可以看到,我們在求各個OPT的時候,左右分支上會出現Overlap Subproblem(重復子問題)。這個結構和斐波那契數列的遞歸求解是一樣的。時間復雜度是O(2^n)。所以我們可以在計算過程中將這些子問題的結果記錄下來(利用列表等)。
4.計算結果
這樣的話,我們求解OPT的時間復雜度就變成了O(n)。
我們計算的過程中,還可以將每一步的最優選擇記錄下來:
5.Python實現代碼
import numpy as np def earnMore(data): data_len = len(data) # prev記錄如果要做某個工作(index),前面最近的不沖突的工作的編號 prev_list = np.zeros(9, dtype=int) # 是否選擇某份工作(index)時,能夠達到的最大收益 opt_list = np.zeros(9, dtype=int) # 首先計算各個工作對應的prev,從8開始算 for i in range(len(data) - 1, 0, -1): start_time = data[i]['time'][0] for k in range(i - 1, 0, -1): end_time = data[k]['time'][1] if end_time <= start_time: prev_list[i] = k break # 從1開始計算OPT,記錄到opt_list中 for i in range(1, len(data)): if i == 1: opt_list[i] = data[i]['val'] continue if prev_list[i] == 0: opt_list[i] = max(data[i]['val'], opt_list[i - 1]) else: opt_list[i] = max(data[i]['val'] + opt_list[prev_list[i]], opt_list[i - 1]) return opt_list if __name__ == '__main__': data = [ {}, {'val': 5, 'time': [1, 4]}, {'val': 1, 'time': [3, 5]}, {'val': 8, 'time': [0, 6]}, {'val': 4, 'time': [4, 7]}, {'val': 6, 'time': [3, 8]}, {'val': 3, 'time': [5, 9]}, {'val': 2, 'time': [6, 10]}, {'val': 4, 'time': [8, 11]} ] res = earnMore(data) print("規定時間內,所能達到的最大收入為",res[-1])
##