成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇學(xué)會理解動態(tài)規(guī)劃

開發(fā) 前端
動態(tài)規(guī)劃是一種比較難以理解的算法思想,本文結(jié)合自己的理解采用通俗易懂的方式來講解下動態(tài)規(guī)劃,歡迎各位感興趣的開發(fā)者閱讀本文。

[[421842]]

本文轉(zhuǎn)載自微信公眾號「神奇的程序員」,作者神奇的程序員。轉(zhuǎn)載本文請聯(lián)系神奇的程序員公眾號。

前言

動態(tài)規(guī)劃是一種比較難以理解的算法思想,本文結(jié)合自己的理解采用通俗易懂的方式來講解下動態(tài)規(guī)劃,歡迎各位感興趣的開發(fā)者閱讀本文。

思路分析

接下來,我們通過一個例子來逐步分析,引出動態(tài)規(guī)劃思想。

假設(shè),你家里三種面值的鈔票(1元、5元、11元)無數(shù)張,現(xiàn)在需要用這些鈔票湊出某個金額出來,我們需要怎樣搭配才能用最少的鈔票數(shù)量湊出這個金額出來?

例如:我們要湊15元出來。

貪心思想 - 只顧眼前

依據(jù)我們的生活經(jīng)驗,肯定是先用面紙較大的鈔票,用總金額做減法運算,思路如下:

  • 先拿1張11元的鈔票,接下來我們要湊的金額就是4元(15 - 11)
  • 要湊4元出來,我們只能用1元鈔票來湊,我們需要4張1元紙幣(4 - 1 - 1 - 1 - 1)

15元湊出來了,我們總共用了5張鈔票(1張11元的,4張1元的)。這種策略,我們稱之為貪心,我們把要湊的金額設(shè)為w,需要用的鈔票面額設(shè)為m,貪心策略會盡快的讓w變的更小,每減少一次,我們接下來要面對的局面就是湊出w - m。

經(jīng)過我們大腦計算后,這種策略湊出15元所用的鈔票數(shù)量并不是最少的,我們直接使用3張5元的鈔票就可以湊這個金額。

更好的方案 - 動態(tài)規(guī)劃

我們使用貪心思想來解決這個問題時,只考慮了眼前的情況,格局小了,那么我們現(xiàn)在應(yīng)該如何避免這種情況呢?

如果使用暴力枚舉的方法來湊出金額,明顯時間復(fù)雜度過高,太多種組合方式可以湊出這個金額了,枚舉它們的時間是不可承受的。

重疊子問題

接下來,我們來嘗試著,找一下這個問題的性質(zhì)。

  • 一開始,如果我們?nèi)×嗣嬷禐?1的鈔票,那么接下來面臨的問題就是湊出金額為4時所需的最少鈔票數(shù)量
  • 一開始,如果我們?nèi)×嗣嬷禐?的鈔票,那么接下來面臨的問題就是湊出金額為10時所需的最少鈔票數(shù)量
  • 一開始,如果我們?nèi)×嗣嬷禐?的鈔票,那么接下來面臨的問題就是湊出金額為14時所需的最少鈔票數(shù)量

經(jīng)過上述分析,我們會發(fā)現(xiàn)這些問題都有一個共同的形式:給定一個金額,湊出這個金額所需的最少鈔票數(shù)量。

我們將一個大問題拆解成了三個子問題。

接下來,我們再來分析下這三個子問題,我們用f(n)來表示湊出n所需的最少鈔票數(shù)量,用cost來表示湊出w所需的鈔票數(shù)量,那么:

如果我們?nèi)×?1,最后用掉的鈔票總數(shù)就為:cost = f(4) + 1

如果我們?nèi)×?,最后用掉的鈔票總數(shù)就為:cost = f(10) + 1

如果我們?nèi)×?,最后用掉的鈔票總數(shù)就為:cost = f(14) + 1

觀察上述問題后,我們會發(fā)現(xiàn)一個共同點:每取一個面值的鈔票,都需要計算剩余金額所需的最少鈔票數(shù),而它們的計算方法都是相同的。

這三個子問題都需要用同一種方式求解,那么它們就屬于重疊子問題。

最優(yōu)子結(jié)構(gòu)

當(dāng)我們要湊出15元的金額時,我們需要的鈔票總數(shù)就為上述三種情況里所需鈔票數(shù)量最少的那一個。

我們在求f(n)時,又要算出金額為n時所需的最少鈔票數(shù),例如f(10),我們只能用2種面值的鈔票(5元的和1元的)

如果用5元來湊的話,我們需要的鈔票數(shù)就為:f(5) + 1

如果用1元來湊的話,我們需要的鈔票數(shù)就為:f(9) + 1

我們在求f(n)時,一定會從其子問題的解決方案中找出所需硬幣數(shù)量最少的那個,即:

f(n) = min(f(n - 1), f(n -5 ), f(n - 11)) + 1

大問題的最優(yōu)解可以由子問題的最優(yōu)解推出,這個性質(zhì)就稱為最優(yōu)子結(jié)構(gòu)。

無后效性

通過上面的分析,我們知道了金額為15時,需要求出3個重疊子問題的解,選出最優(yōu)的那個就是最終問題的解。

那三個子問題又有自己的重疊子問題,我們在求解這些重疊子問題時,只需要知道最終答案,不用去關(guān)心他們是如何算出來的,因為他們的計算過程并不會對之后的問題產(chǎn)生影響。

例如:f(4), f(10), f(14) ,我們只需要求出他們的具體值,他們的計算過程對我們之后要求解的問題沒有影響。

如果給定某一階段的狀態(tài),這一階段以后過程的發(fā)展,不受這階段以前各段狀態(tài)的影響,就稱為無后效性,即:未來與過去無關(guān)。

剪繩子

有一根長度為n的繩子,把繩子剪成m段(m、n都是整數(shù),n > 1并且m > 1),每段繩子的長度記為k[0], k[1], ..., k[m]。

請問k[m] * k[1] * ... * k[m]可能的最大乘積是多少?

例如:當(dāng)繩子長度為8時,我們把它剪成長度分別為2、3、3的三段,此時得到的最大乘積是18。

思路分析

接下來,我們來分析這個例子,看看能否用動態(tài)規(guī)劃來解決。

根據(jù)題意,我們可知下述信息:

  • 繩子的長度肯定大于1且每次至少切一刀
  • 我們用f(n)來表示長度為n的繩子所有切法的最大乘積

那么,當(dāng)繩子的長度為2時,我們只有一種切法,從中間切,這條繩子會被切為長度各為1的兩小段,如下圖所示:

當(dāng)n=2時,f(n) = 1 * 1 = 1,即:f(2) = 1

我們繼續(xù)分析n=3的情況,如下圖所示,它有2種切法

  • 切成長度為1和長度為2的兩小段
  • 切成長度分別為1、1、1的三小段

從切法2中,我們可以看出它其實就是對長度為2的繩子進(jìn)行了一次切分,經(jīng)過前面的分析,我們已經(jīng)知道了它的所有切法的最大乘積是1,那么他們的乘積就是1 * 1 * 1 = 1。

因此, 我們不對他進(jìn)行劃分,直接取切法1的乘積,即: f(3) = 2

我們再來看下n=4的情況,如下圖所示,它有3種切法

  • 切成長度為1和長度為3的兩小段
  • 切成長度為2和長度為2的兩小段
  • 切長度為別為1的四小段

從切法1中,我們可以看出長度為3的那一段繩子的最大乘積我們已經(jīng)算出來了(f(3) = 2),如果我們對這段繩子進(jìn)行切分的話,乘積就變小了,所以我們選擇不切分這部分繩子,那么切法1的最大乘積就為1 * 3 = 3。

從切法2中,我們可以看出那兩段繩子的長度都為2,而長度為2的繩子的最大乘積我們也已經(jīng)算出來了(f(2) = 1),如果切分的話,乘積就變小了,那么切法2的最大乘積就為2 * 2 = 4。

綜上所述,我們可以發(fā)現(xiàn)這樣一條規(guī)律:

  • 無論繩子最終被切成多少段,它肯定是一刀一刀來切分的
  • 繩子長度為2或者3時,不會再進(jìn)行切分了,直接取其長度

那么,對于一條繩子來說,它一旦被切了一刀,就會被劃分為2個子問題:

  • 切割點左側(cè)的繩子怎么切
  • 切割點右側(cè)的繩子怎么切

因為我們是從1開始往后推的,前面的繩子怎么切,我們已經(jīng)存起來了。

分析到這里,我們發(fā)現(xiàn)它已經(jīng)滿足動態(tài)規(guī)劃的2個特性了:

  • 重疊子問題:繩子被切開后,每一部分的繩子還可能再繼續(xù)進(jìn)行切分,每次切分要面臨的子問題都是一樣的
  • 最優(yōu)子結(jié)構(gòu):對每部分的繩子進(jìn)行切分時,我們都需要求出這部分繩子的所有切法中最大乘積是多少,滿足了最優(yōu)子結(jié)構(gòu)這個特性

我們再來分析下n=5的情況,如下圖所示,我們的第一刀有兩種切法:從1位置、從2位置切,分別可以將繩子切為:

  • 長度為1和長度為4的兩小段
  • 長度為2和長度為3的兩小段

我們用一個數(shù)組(result)將前面已經(jīng)求出來的繩子的最大乘積(最優(yōu)解)存起來,綜上所述,我們知道了當(dāng)繩子長度小于4時,每段繩子長度的最大乘積是固定的,即:

  • result[0] = 0
  • result[1] = 1
  • result[2] = 2
  • result[3] = 3

注意:因為繩子至少要切一刀且繩子的長度大于1,所以當(dāng)繩子長度為1時是沒法進(jìn)行裁切的,因此它的最大乘積為1。

當(dāng)繩子長度為2或3時,我們不會對它進(jìn)行切分,因此他們的最大乘積就是其本身的長度。

觀察上圖后我們發(fā)現(xiàn),當(dāng)繩子長度大于等于4時,第一刀要的位置切法最多只能切到繩子長度一半的位置,每次切分出來的子問題,我們在前面已經(jīng)算過并且放進(jìn)了result中,我們只需將每種切法的子問題最優(yōu)解相乘取出最大值即可。

當(dāng)n=4時,第一刀可以切的位置可以是:1、2:

  1. result[4] = max(result[1] * result[3], result[2] * result[2]) 
  2.           = max(1 * 3, 2 * 2) 
  3.         = max(3, 4) 
  4.           = 4 

當(dāng)n=5時,第一刀可以切的位置可以是:1、2:

  1. result[5] = max(result[1] * result[4], result[2] * result[3]) 
  2.          = max(1 * 4, 2 * 3 ) 
  3.           = max(4, 6) 
  4.           = 6 

當(dāng)n = 6時,第一刀可以切的位置可以是:1、2、3

  1. result[6] = max(result[1] * result[5], result[2] * result[4], result[3] * result[3]) 
  2.      = max(1 * 6, 2 * 4, 3 * 3) 
  3.      = max(6, 8, 9) 
  4.      = 9 

研究到這里,我們會發(fā)現(xiàn)在求子問題的最優(yōu)解時,我們只關(guān)心它的結(jié)果,它的計算過程并不會影響到我們最終問題的解,那么它也滿足了動態(tài)規(guī)劃的最后一個性質(zhì):無后效性

遞推公式

經(jīng)過上面的一系列的推導(dǎo),我們發(fā)現(xiàn)這個問題已經(jīng)滿足了動態(tài)規(guī)劃的三個性質(zhì),那么也就是說這個問題是可以用動態(tài)規(guī)劃來解決的。

經(jīng)過上面的分析,我們知道了不管怎么切,繩子都會被切成兩部分,然后再分別求解這兩部分的最大乘積,那么當(dāng)繩子長度為n時,我們就能得到如下所示的公式:

  • result[n] = result[i] * result[n - i]

n為當(dāng)前要求的繩子長度,i為當(dāng)前要切割位置。

繩子的不管從哪個位置切,都會被切成兩段,我們求出這兩段的乘積,求出每種切法的最大乘積就是長度為n的繩子的最大乘積。

實現(xiàn)代碼

經(jīng)過上面的一系列分析,我們已經(jīng)徹底掌握了這道題的解法,思路有了,我們就可以用代碼將其實現(xiàn)了,如下所示(TypeScript代碼):

  1. public cutTheRope(length: number): number { 
  2.     // 由于繩子只能整數(shù)切,所以繩子長度小于2時,無法進(jìn)行裁剪 
  3.     if (length < 2) { 
  4.       return 0; 
  5.     } 
  6.     // 繩子長度為2時,只能從中間裁剪, 所有切法的最大乘積為1 
  7.     if (length === 2) { 
  8.       return 1; 
  9.     } 
  10.     // 繩子長度為3時,可以切成: 
  11.     // 1. 1 1 1 
  12.     // 2. 1 2 
  13.     // 1 * 2 > 1 * 1 * 1, 故長度為3時, 所有切法的最大乘積為2 
  14.     if (length === 3) { 
  15.       return 2; 
  16.     } 
  17.  
  18.     // 創(chuàng)建結(jié)果數(shù)組,存儲每段長度繩子切分時的最大乘積 
  19.     const products = new Array<number>(length + 1); 
  20.     // 長度小于4時,繩子的最大乘積我們已經(jīng)推算出來了,因此直接保存即可 
  21.     products[0] = 0; 
  22.     products[1] = 1; 
  23.     // 繩子長度為2或3時,不進(jìn)行拆分,最大乘積為繩子的長度 
  24.     products[2] = 2; 
  25.     products[3] = 3; 
  26.  
  27.     // 繩子長度大于4時需要對繩子進(jìn)行切分,求出切分后的每段繩子的最大乘積 
  28.     for (let i = 4; i <= length; i++) { 
  29.       // 賦初始值 
  30.       products[i] = 0; 
  31.       // 當(dāng)前長度繩子的最大乘積 
  32.       let max = 0; 
  33.       // 至少切一刀,從繩子的1位置開始切,切到i/2位置。 
  34.       // 求出長度為i時,切一刀后兩段繩子的最大乘積 
  35.       for (let j = 1; j <= i / 2; j++) { 
  36.         // 根據(jù)遞推公式求當(dāng)前裁剪位置的兩段繩子的乘積 
  37.         const product = products[j] * products[i - j]; 
  38.         // 比對歷史切法中兩段繩子的最大乘積和當(dāng)前切法兩段繩子的乘積 
  39.         if (max < product) { 
  40.           // 替換最大值 
  41.           max = product; 
  42.         } 
  43.         // 修改當(dāng)前繩子長度切法的最大乘積 
  44.         products[i] = max
  45.       } 
  46.     } 
  47.     // 每種長度繩子的最大乘積都已經(jīng)求出,length位置的值就是整個問題的解 
  48.     return products[length]; 

完整代碼請移步:DynamicProgramming.ts[1]

編寫測試用例

我們將題目中求長度為8的繩子帶入帶入上述代碼中,求出最大值來驗證下我們的代碼是否是正確的,如下所示:

  1. import DynamicProgramming from "../DynamicProgramming.ts"
  2.  
  3. const dynamicProgrammingTest = new DynamicProgramming(); 
  4. const maxVal = dynamicProgrammingTest.cutTheRope(8); 
  5. console.log("最大值為", maxVal); 

完整代碼請移步:DynamicProgramming-test.ts[2]

運行結(jié)果如下:

同類型例子

當(dāng)你讀完本文后,相信你對動態(tài)規(guī)劃已經(jīng)有了一定的理解,緊接著你可以嘗試的做一下其他能用動態(tài)規(guī)劃解決的問題,加深下理解,達(dá)到徹底掌握的目的。

我還寫了其他幾個動態(tài)規(guī)劃問題的分析文章,如果你對此感興趣,請移步:

  • 最少硬幣找零問題[3]
  • 背包問題[4]
  • 最長公共子序列[5]
  • 矩陣鏈相乘[6]

寫在最后

至此,文章就分享完畢了。

我是神奇的程序員,一位前端開發(fā)工程師。

公眾號無法外鏈,如果文中有鏈接,可點擊下方閱讀原文查看??

參考資料

[1]DynamicProgramming.ts: https://github.com/likaia/algorithm-practice/blob/a31acfe5c9b3acbf51c9383a09003611b64ea16b/src/DynamicProgramming.ts#L9

[2]DynamicProgramming-test.ts: https://github.com/likaia/algorithm-practice/blob/7fda8ff39d15ab7a4030bd998a63e1ec331117c9/src/test-case/DynamicProgramming-test.ts

[3]最少硬幣找零問題: https://juejin.cn/post/6869571836066299912#heading-7

[4]背包問題: https://juejin.cn/post/6869571836066299912#heading-10

[5]最長公共子序列: https://juejin.cn/post/6869571836066299912#heading-13

[6]矩陣鏈相乘: https://juejin.cn/post/6869571836066299912#heading-16

 

責(zé)任編輯:武曉燕 來源: 神奇的程序員
相關(guān)推薦

2010-08-20 14:25:47

錯誤消息路由器

2022-01-01 20:02:25

Metadata動態(tài)元數(shù)據(jù)

2022-01-02 08:43:46

Python

2022-02-07 11:01:23

ZooKeeper

2021-09-30 11:55:00

微服務(wù)

2021-07-02 09:45:29

MySQL InnoDB數(shù)據(jù)

2021-07-06 08:59:18

抽象工廠模式

2023-01-03 08:31:54

Spring讀取器配置

2021-07-05 22:11:38

MySQL體系架構(gòu)

2021-05-11 08:54:59

建造者模式設(shè)計

2022-08-26 09:29:01

Kubernetes策略Master

2022-08-23 08:00:59

磁盤性能網(wǎng)絡(luò)

2023-11-28 08:29:31

Rust內(nèi)存布局

2021-09-28 08:59:30

復(fù)原IP地址

2021-10-14 10:22:19

逃逸JVM性能

2022-04-12 08:30:52

回調(diào)函數(shù)代碼調(diào)試

2021-07-16 22:43:10

Go并發(fā)Golang

2023-03-13 21:38:08

TCP數(shù)據(jù)IP地址

2021-10-27 09:59:35

存儲

2022-10-20 07:39:26

點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 涩爱av一区二区三区 | 91亚洲精品久久久电影 | 欧美国产精品一区二区 | 欧美精品久久久久久久久久 | 夜久久 | 国产精品一二三区在线观看 | 91亚洲欧美| 国产在线一区二区 | 亚洲第一av | 亚洲成人午夜电影 | 自拍偷拍中文字幕 | 久久伊人精品 | 在线免费观看亚洲 | 亚洲入口| 久久久久国产一区二区三区 | 欧美性视频在线播放 | 亚洲午夜小视频 | 亚洲综合色视频在线观看 | 日韩免费在线 | 91精品国产91综合久久蜜臀 | 国产精品明星裸体写真集 | 久久久国产一区二区三区 | 日本午夜视频 | 中文字幕在线免费观看 | 色综合欧美 | 国产日韩欧美中文 | 国产区精品 | 亚洲精品电影网在线观看 | 欧美aaa级| 亚洲国产精品一区二区久久 | 午夜视频在线观看视频 | 麻豆成人在线视频 | 天堂va在线观看 | 中文字幕日韩欧美一区二区三区 | 久久免费视频观看 | 国产精品久久久久久久久久免费看 | www网站在线观看 | 狠狠久| www.色53色.com | 精品视频一区二区 | 黄网站在线播放 |