[Python] 算法心得—動態(tài)規(guī)劃

1. 硬幣組合

如果我們有面值為1元、3元和5元的硬幣若干枚窿吩,如何用最少的硬幣湊夠11元?

參考資料

假設(shè)d[i]為湊滿i元所需最少的硬幣數(shù)倾哺,那么:

d[i] 解釋
d[0] = 0 0
d[1] = 1 需要從面值不大于1元的硬幣中選擇,結(jié)果為1
d[2] = d[2-1] + 1 = 2 從面值不大于2元的硬幣中選擇,符合要求的硬幣面值為:1元
d[3] = d[3-3] + 1 = 1 符合要求的硬幣面值為:1元/3元菜循,含有3元的硬幣d[3]=d[3-3]+1=1癌幕,不含3元的硬幣d[3]=d[3-1]+1=d[2]+1 =3
... ...

可以得出狀態(tài)轉(zhuǎn)移方程:dp[i] = min(d[i-value[j]) + 1昧穿,參考代碼:

import sys

# 需要用硬幣湊滿的錢數(shù)
amount = 12
# 硬幣的種類
coins = [1, 3, 5]

def coin_dynamic(amount, coins):
    dp = [0]
    for i in range(1, amount + 1):
        dp.append(sys.maxsize)
        for j in range(len(coins)):
            if coins[j] <= i and dp[i - coins[j]] + 1 < dp[i]:
                dp[i] = dp[i - coins[j]] + 1
    return dp

2. 0-1背包問題

假設(shè)我們有n件物品胶逢,分別編號為1, 2...n初坠。其中編號為i的物品價值為value[i]彭雾,它的重量為weight[i]半沽。為了簡化問題者填,假定價值和重量都是整數(shù)值♂M校現(xiàn)在重挑,假設(shè)我們有一個背包,它能夠承載的重量是capacity∶В現(xiàn)在史煎,我們希望往包里裝這些物品篇梭,使得包里裝的物品價值最大化恬偷,那么我們該如何來選擇裝的東西呢坦康?

參考資料

首先我們先構(gòu)建一個表格dp[i][j]滞欠,橫軸為背包的容納重量(從1到背包的實際最大容納),縱軸為各個可選擇的物品肆良。而表格中的每個單元格表示的是使用i與前的物品、且保證總重量不大于j情況下背包能容納物品的最大價值隧哮。

嘗試填充完畢后曲秉,我們可以得到一個結(jié)論:在i行j列的最大值可以說是(i-1行[即不取i物品]j列的值) 和 (i物品的價值 + i-1行j-i物品價值列的值[即取了i物品的價值])榆鼠,寫成狀態(tài)轉(zhuǎn)移方程即為:`dp[i][j] = max{dp[i-1][j], dp[i-1][j-value[i]] + value[i]}妆够,對應(yīng)的代碼如下:

# n個物體的重量(w[0]無用)
weight = [1, 4, 3, 1]
# n個物體的價值(p[0]無用)
value = [1500, 3000, 2000, 2000]
# 計算n的個數(shù)
n = len(weight) - 1
# 背包的載重量
capacity = 5
# 裝入背包的物體的索引
x = []

def bag_dynamic(weight, value, capacity, x):
    n = len(weight)
    weight.insert(0, 0)
    value.insert(0, 0)

    dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)]

    for i in range(1, n + 1):
        for j in range(1, capacity + 1):
            if j >= weight[i]:
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
            else:
                dp[i][j] = dp[i - 1][j]
    j = capacity
    for i in range(n, 0, -1):
        if dp[i][j] > dp[i - 1][j]:
            x.append(i)
            j = j - weight[i]

    return dp[n][capacity]

3. CPU雙核問題

雙核CPU的兩個核能夠同時的處理任務(wù)家妆,現(xiàn)在有n個已知數(shù)據(jù)量的任務(wù)需要交給CPU處理,假設(shè)已知CPU的每個核1秒可以處理1kb伤极,每個核同時只能處理一項任務(wù)蛹找。n個任務(wù)可以按照任意順序放入CPU進行處理庸疾,現(xiàn)在需要設(shè)計一個方案讓CPU處理完這批任務(wù)所需的時間最少炊豪,求這個最小的時間牵舱。

要使CPU處理時間最少,就要充分利用其雙核芜壁,使其達到負載均衡。所以該問題的實質(zhì)就是將數(shù)組分成兩部分塞淹,使得兩部分的和的差最小窟蓝。

weight = [0, 3072, 3072, 7168, 3072, 1024]  # 假設(shè)進入處理的的任務(wù)大小
weight = list(map(lambda x: int(x / 1024), weight))  # 轉(zhuǎn)化下
value = weight  # 這題的價值和任務(wù)重量一致
capacity = int(sum(weight) / 2 + 1)  # 背包承重為總?cè)蝿?wù)的一半


def dynamic_cpu(weight, value, capacity):
    dp = [[0 for _ in range(capacity + 1)] for _ in range(len(weight))]

    for i in range(1, len(value)):
        for j in range(1, capacity + 1):
            if j >= value[i]:
                dp[i][j] = max(dp[i - 1][j], value[i] + dp[i - 1][j - weight[i]])
            else:
                dp[i][j] = dp[i - 1][j]
    return dp[-1][-1]


print(dynamic_cpu(weight, value, capacity))

4. LIS-最長非降子序列

一個序列有N個數(shù):A[1],A[2],…,A[N]运挫,求出最長非降子序列的長度(子序列不必連續(xù),但不能破壞原序列的順序匈挖,子串必須要連續(xù))翘瓮。

參考文章

定義d[i]為A[i]位置上的最長非降子序列長度踊赠,所以:

 d[0] = 1  
 d[1] = d[0] + 1 (A[1] >= A[0])  
      = 1 (A[1] < A[0])
 d[2] = d[1] + 1 (A[2] >= A[1])
      = d[0] + 1 (A[0] <= A[2] <= A[1])
      = 1 (A[2] < A[0], A[2] < A[1])
 ......

所以可以得到狀態(tài)轉(zhuǎn)移方程:d[i] = max{1, d[j]+1}(其中j<i, A[j]<=A[i])筐带,代碼如下:

def lis(arr):
    num = len(arr)
    d = [0] * num
    max_len = 1
    for i in range(num):
        d[i] = 1
        for j in range(i):
            if arr[j] <= arr[i] and d[i] < d[j] + 1:
                d[i] = d[j] + 1
            if d[i] > max_len:
                max_len = d[i]
    return max_len


print(lis([2, 1, 5, 3, 6, 4, 8, 9, 7]))

此時的時間復(fù)雜度為O(N^2)缤灵,還有一種巧妙的方法可以將復(fù)雜度降為O(n * logN)。

定義suq為最長遞增子序列腮出,它是一個有序數(shù)組胚嘲。

step 1,將arr[0]有序放入suq娶吞,則suq=[2];

step 2材部,將arr[1]有序放入乐导,因為arr[1]<suq[-1](小于有序數(shù)組最后一個數(shù))晋涣,所以將suq[-1]替換為arr[1]蔼夜,suq=[1];

step 3求冷,將arr[2]有序放入,此時arr[2]>suq[-1](大于有序數(shù)組最后一個數(shù))窍霞,所以將suq尾部添加arr[2]匠题,suq=[1,5];

step 4,將arr[3]有序放入但金,此時如step 2情況梧躺,suq=[1,3];

step 5,同step 3傲绣,suq=[1,3,6];

step 6,同step 2巩踏,suq=[1,3,4];

step 7秃诵,同step 3,suq=[1,3,4,8];

step 8塞琼,同step 3菠净,suq=[1,3,4,8,9];

step 9,同step 2彪杉,將arr[8]替換suq[3]毅往,suq=[1,3,4,7,9]

所以最長遞增子序列長度為len(suq)=5。

上述的插入分兩種情況派近,第一種:arr[i]>=suq[-1]攀唯,尾插;第二種:arr[i]<suq[-1]渴丸,將arr[i]替換掉suq中比其大的第一個元素(此時可以用二分法查找)侯嘀,每一個插入復(fù)雜度為O(logN),總復(fù)雜度為O(N * logN)谱轨。

def binary_search(suq, l, h, key):
    if suq[h] <= key:
        return h+1
    while l < h:
        mid = (l+h) // 2
        if suq[mid] <= key:
            l = mid + 1
        else:
            h = mid
    return l

def lis_bs(arr):
    num = len(arr)
    suq = arr[0:1]
    max_len = 1
    for i in range(1, num):
        point = binary_search(suq, 0, max_len-1, arr[i])
        if point <= max_len - 1:
            suq[point] = arr[i]
        else:
            suq.insert(point, arr[i])
            max_len += 1
    return max_len

print(lis_bs([2, 1, 5, 3, 6, 4, 8, 9, 7]))

5. LCS-最長公共子序列

給定序s1=[1,3,4,5,6,7,7,8],s2=[3,5,7,4,8,6,7,8,2]戒幔,s1和s2的相同子序列,且該子序列的長度最長土童,即是LCS诗茎。

參考文章

可以得出以下結(jié)論:

如果s1和s2的最后一個元素相等,那么s1和s2的LCS就等于除去最后一個元素的s1和除去最后一個元素的s2的LCS再加上它們相等的最后一個元素献汗;

如果s1和s2最后一個元素不想等敢订,那么其LCS就等于(除去最后一個元素的s1和s2的LCS)和(除去最后一個元素的s2和s1的LCS)中更大的一個王污。

代碼如下:

s1 = [1, 3, 4, 5, 6, 7, 7, 8]
s2 = [3, 5, 7, 4, 8, 6, 7, 8, 2]


def lcs(s1, s2):
    dp = [[0] * (len(s2) + 1) for _ in range(len(s1) + 1)]

    for i in range(1, len(s1) + 1):
        for j in range(1, len(s2) + 1):
            if s1[i - 1] == s2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return dp[-1][-1]


print("max LCS number:", lcs(s1, s2))

6. 組成SUM的方案數(shù)

給定一個有n個正整數(shù)的數(shù)組A和一個整數(shù)sum,求選擇數(shù)組A中部分數(shù)字和為sum的方案數(shù)。
當(dāng)兩種選取方案有一個數(shù)字的下標不一樣,我們就認為是不同的組成方案枢析。

同樣玉掸,我們用dp[i][j]表示用前i個數(shù)字湊到j(luò)最多有多少種方案,那么可得:

# 當(dāng)不用i個數(shù)字時能湊到j(luò)的最多情況
dp[i][j] = dp[i-1][j]
# 用了i時醒叁,只需看原來湊到j(luò)-arr[i]的最多情況
dp[i][j] += dp[i-1][j-arr[i]]

代碼如下:

def sum_combination(arr, sum):
    length = len(arr)

    dp = [[1] + [0] * sum for _ in range(length + 1)]

    for i in range(1, length + 1):
        for j in range(1, sum + 1):
            if j - arr[i - 1] >= 0:
                dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i - 1]]
            else:
                dp[i][j] = dp[i - 1][j]
    return dp[length][sum]


print(sum_combination([5, 5, 10, 2, 3], 10))

7. 連續(xù)子數(shù)組的最大和

上一章數(shù)組篇談到過這個問題司浪,下面我們看一下如何能用一種更簡便更易理解的方式來解決吧。

定義dp[i]表示以第i個元素為結(jié)尾的連續(xù)子數(shù)組的最大和把沼,則有狀態(tài)方程 dp[i]=max{dp[i-1]+a[i], a[i]}啊易。

代碼如下:

def max_subarray(arr):
    dp = [arr[0]] + [0] * (len(arr)-1)

    for i in range(1, len(arr)):
        dp[i] = max(dp[i-1]+arr[i], arr[i])

    return max(dp)

print(max_subarray([-1,2, 1]))

8. 暗黑數(shù)

這道題超酷的!

一個只包含’A’、’B’和’C’的字符串饮睬,如果存在某一段長度為3的連續(xù)子串中恰好’A’租谈、’B’和’C’各有一個,那么這個字符串就是純凈的捆愁,否則這個字符串就是暗黑的割去。例如:

BAACAACCBAAA 連續(xù)子串”CBA”中包含了’A’,’B’,’C’各一個,所以是純凈的字符串

AABBCCAABB 不存在一個長度為3的連續(xù)子串包含’A’,’B’,’C’,所以是暗黑的字符串

你的任務(wù)就是計算出長度為n的字符串(只包含’A’昼丑、’B’和’C’)呻逆,有多少個是暗黑的字符串。

從第一位開始不斷往后推算暗黑數(shù)的排列總數(shù)菩帝,定義f(n)為n位上暗黑數(shù)的排列總數(shù)咖城,假設(shè)S(n)為n與n-1相同的排列數(shù),D(n)為n與n-1不同的排列數(shù)呼奢。

那么宜雀,可以得到f(n-1) = S(n-1) + D(n-1) (1)。

如果n-1與n-2相同握础,那么新增字母ABC都可以辐董,有3*S(n-1)種可能;如果n-1與n-2不同弓候,那么新增字母只能是n-1與n-2中的一個郎哭,有2*D(n-1)種可能;

由此可得:f(n) = 3\*S(n-1) + 2\*D(n-1) = 2\*f(n-1) + S(n-1) (2)

我們繼續(xù)分析菇存,如果n-1與n-2相同夸研,那么之后有S(n-1)種可能是n與n-1相同的,有2*S(n-1)種可能是n與n-1不同的依鸥;如果n-1與n-2不同亥至,有D(n-1)種可能是n與n-1相同的,D(n-1)種可能是n與n-1不同的。

由此又可得 S(n) = S(n-1) + D(n-1)姐扮,結(jié)合上述式(1) f(n-1) = S(n-1) + D(n-1)可得: S(n) = f(n-1)絮供。

代入式(2)可得:f(n) = 2*f(n-1) + f(n-2)

代碼水到渠成:

# 遞推
def dark_num(arr):
    dp = []
    dp[0], dp[1] = 3, 9
    if len(arr) < 3:
        return dp[len(arr)-1]
    dp += [0] * (len(arr) - 2)
    for i in range(2, len(arr)):
        dp[i] = 2 * dp[i-1] + dp[i-2]
    return dp[len(arr)-1]
    
 # 遞歸
 def dark_num(arr):
    if arr == 1:
        return 3
    elif arr==2:
        return 9
    else:
        return 2*dark_num(arr-1) + dark_num(arr-2)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茶敏,一起剝皮案震驚了整個濱河市壤靶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惊搏,老刑警劉巖贮乳,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恬惯,居然都是意外死亡向拆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門酪耳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浓恳,“玉大人,你說我怎么就攤上這事碗暗【苯” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵言疗,是天一觀的道長吆鹤。 經(jīng)常有香客問我,道長洲守,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任沾凄,我火速辦了婚禮梗醇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撒蟀。我一直安慰自己叙谨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布保屯。 她就那樣靜靜地躺著手负,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姑尺。 梳的紋絲不亂的頭發(fā)上竟终,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音切蟋,去河邊找鬼统捶。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喘鸟。 我是一名探鬼主播匆绣,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼什黑!你這毒婦竟也來了崎淳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤愕把,失蹤者是張志新(化名)和其女友劉穎拣凹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體礼华,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡咐鹤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了圣絮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祈惶。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扮匠,靈堂內(nèi)的尸體忽然破棺而出捧请,到底是詐尸還是另有隱情,我是刑警寧澤棒搜,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布疹蛉,位于F島的核電站,受9級特大地震影響力麸,放射性物質(zhì)發(fā)生泄漏可款。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一克蚂、第九天 我趴在偏房一處隱蔽的房頂上張望闺鲸。 院中可真熱鬧,春花似錦埃叭、人聲如沸摸恍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽立镶。三九已至,卻和暖如春类早,著一層夾襖步出監(jiān)牢的瞬間媚媒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工涩僻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留欣范,地道東北人变泄。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像恼琼,于是被迫代替她去往敵國和親妨蛹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 動態(tài)規(guī)劃(Dynamic Programming) 本文包括: 動態(tài)規(guī)劃定義 狀態(tài)轉(zhuǎn)移方程 動態(tài)規(guī)劃算法步驟 最長...
    廖少少閱讀 3,283評論 0 18
  • 在C語言中,五種基本數(shù)據(jù)類型存儲空間長度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 3,345評論 0 2
  • 回溯算法 回溯法:也稱為試探法晴竞,它并不考慮問題規(guī)模的大小蛙卤,而是從問題的最明顯的最小規(guī)模開始逐步求解出可能的答案,并...
    fredal閱讀 13,660評論 0 89
  • 《浪淘沙》 唐 · 白居易 借問江潮與海水噩死,何似君情與妾心颤难? ...
    zxz緣來緣盡閱讀 524評論 0 0
  • 學(xué)校的教學(xué)樓門洞底下有一只狗,這只個狗和別的狗不太一樣已维。我觀察了很久行嗤,它總是成天的趴在門洞底下,天氣熱了就趴在陰影...
    鹽是甜的閱讀 307評論 0 0