還在看算法的朋友,你需要一篇:動態規劃

2021-01-19 碼農登陸

前言

最近有小夥伴在後臺留言說,可不可以聊一聊動態規劃。這是一個有趣的話題,因為對於大部分業務型公司來說,面試中的算法部分並不會考這一塊。但是BAT等一線網際網路公司又不一定不會考,比如我在面試頭條的時候就被問了一道動態規劃的題目。

此外,我個人覺得動態規劃有趣的原因是,我認為應用層的工程師能接觸到或者用到的「最需要思考」的算法題目了。所以咱們今天就好好聊一聊動態規劃。

正文

一、貪心算法

聊動態規劃之前,我想先聊一聊貪心算法。

1.1、什麼事貪心算法

一個很經典的題目:我們手裡有1元、5元、10元、50元面值的若干紙幣。我們需要用最少張數拿正好66元錢。我們應該怎麼拿?

在這樣的給定條件下,我們採用每次拿面值最大,直到湊夠66元的策略:1張 * 50元 + 1張 * 10元 + 1張 * 5元 + 1張 * 1元。那麼此時正巧達成題目中的答案。OK,這種策略我們都知道叫做:「貪心」。

1.2、貪心算法的弊端

貪心算法在一定條件下的確能夠得出我們想要的結果。當然我們也很清楚,在一定條件下,貪心算法也並不能得到我們想要的結果。比如:在1元、5元、11元面值的前提下,拿最少張數湊出15元。

在貪心算法的策略下:1張 * 11元 + 4張 * 1元。但是3張 * 5元明顯可以做到更優。

我們很清楚貪心算法的問題出在哪:貪心算法每次會都選擇當前情況下的最優解。但是有時一味地只看重「眼前的利益」,並不一定會「笑到最後」。

貪心算法就是最真實的例子。

1.3、能否改進

既然我們明白貪心算法錯就錯在:每一步只考慮了當下最優解。那麼換個角度來說,是不是只要我們每一步考慮全局最優解就能解決這個問題。

可是轉念一想,每一步考慮全局最優解似乎不大可能。

的確我們想每一步都做到全局最優解的確是不可能。但我們可以換一種思路:我們可以去讓未來告訴我們答案。

這種思想就是動態規劃。

二、動態規劃

2.1、暴力破解?

還是剛才那個題目:在1元、5元、11元面值的前提下,拿最少張數湊出15元。

對於我們來說,我們的任務就是:每一次從1元、5元、11元的錢幣中拿出一張,最少在第幾次拿完湊出了15元。

所以這個問題可以用一張圖來表示:

我們會發現,對於當前的我來說:我除了知道我還需要湊多少錢,以及我拿了幾張錢幣以外我什麼都不清楚(既然如此,我還考慮個屁最優解!)。所以既然我們根本不能提前知道接下來會怎麼樣,那我們就把這個難題交給下一步…因為最後一步終究會湊出15元…我猜小夥伴們讀到這,已經開始罵娘了…tm的,我也明白可以這麼做啊。不就是暴力破解嗎!

2.2、非也

咱們來模擬一下,這裡所謂的「暴力破解」:

情況1:假設第一步我拿了11元,我還需要4元,那麼第二、第三、第四、第五步我只能依次拿1元。最終拿了5張。情況2:假設第一步我拿了5元,我還需要10元,此時我沒辦法拿11元;所以第二步可以選擇拿5元/1元,而第三步也是如此。最好的選擇拿3張5元,最終拿了3張。情況3:假設第一步我拿了1元,我還需要14元,那麼此時擺在我面前的選擇:可以拿11元,也可以拿5元,也可以拿1元。因此對於情況3,我們有更多的選擇去嘗試…假設你嘗試完了所有選擇,你會發現最終拿了5張。此時,不知道大家發沒發現一個現象:對於當下的我來說,當下已經不重要了,因此此時無論怎樣我都會選擇一張錢幣。而真正有意義的操作意味未來我該怎麼選。如果未來的我拿了最少的張數,那麼對我當下的我來說選擇哪個都不重要!

那麼此時,如果我們用函數去表達情況3:定義一個f(n),表示湊n塊錢最少需要拿幾張。對於情況1來說,第一步的函數表達式:f(15) = min(f(15-11) , f(15-5) , f(15-1)) + 1。

這裡的含義:第一步我隨便拿,我只需要保證我拿完之後,接下來拿的張數最少即可。也就是說我不考慮這一步我是拿了11元,還是5元,還是1元。我只關心我拿完之後接下來最少就好,也就是min(f(15-11) , f(15-5) , f(15-1))->min(f(4) , f(10) , f(14))。

而min(f(4) , f(10) , f(14))又可以分別演化成:

f(4) = min(f(4-1))f(10) = min(f(10-5) , f(10-1))f(14) = min(f(14-11) , f(14-5) , f(14-1))所以我們只關心f(n),至於是f(n-11)還是f(n-5)還是f(n-1),我們統統不關心!

2.3、實現

我猜認真思考的小夥伴已經大概明白了這題的思路。這裡貼一個有意思的反向解題代碼,大家加深一下印象:

private lateinit var moneyRecord :IntArray // 這個數組的每個下標存儲:湊足0-n面值的錢,最少使用拿幾張private val coins = intArrayOf(1, 5, 11) // 面值fun funtion(curMoney: Int, allMoney: Int) { // curMoney:當前需要湊的面值,從0開始跌增到allMoney(最終需要湊的面值,也就是15) if (curMoney == 0) { moneyRecord[curMoney] = 0 funtion(curMoney + 1, allMoney) } else { var min = 9999999 // 初始化一個很大的數值。當最後如果得出的結果是這個數時,說明湊不出來。 // 在1、5、11面值中,最少次數能夠湊齊curMoney面值 for (coin in coins) { if (curMoney >= coin && moneyRecord[curMoney - coin] + 1 < min) { min = moneyRecord[curMoney - coin] + 1 } } moneyRecord[curMoney] = min // 遞歸 if (curMoney < allMoney) { funtion(curMoney + 1, allMoney) } }}2.4、總結

動態規劃與暴力破解的區別在哪裡:

暴力破解明顯的特徵,我們需要考慮到所有的邊界條件,這樣帶來的問題就是if特別的多。

而動態規劃所有的邊界條件就是一共有幾種可能。對於動態規劃來說,我們只關係結果,並不關心結果是怎麼算出來的。譬如,求出f(15),只需要知道f(14),f(10),f(4)的值。其他信息並不需要。所以我們就可以將其簡化和相同問題的遞歸。

也就是「學術界」為其總結的倆個性質:

無後效性最優子結構而這就是動態規劃。

尾聲

最近真的忙…忙的都想離職了。不爽!哈哈,但是生活還是要繼續~~衝鴨!

相關焦點

  • 如何掌握動態規划算法的套路?
    實際上,它並不是不是一種確定的算法,它是一種最優化的方法求解問題的思想或方法。它是由美國數學家貝爾曼(Bellman)在研究多階段決策過程的優化問題時提出。不過,與之對應的還有一些與時間無關的靜態規劃,如:線性規劃、非線性規劃等。在運籌學中,動態規劃是的非常重要的內容,在各個行業領域都有著廣泛的應用。我們如何理解動態規劃?
  • Java動態規划算法策略
    Dynamic Programming是五大常用算法策略之一,簡稱DP,譯作中文是「動態規劃」,可就是這個聽起來高大上的翻譯坑苦了無數人,因為看完這個算法你可能會覺得和動態規劃根本沒太大關係,它對「動態」和「規劃」都沒有太深的體現。
  • 拒絕遺忘:高效的動態規划算法
    不懂動態規劃的人會在解決過的問題上再次浪費時間,懂的人則會事半功倍。那麼什麼是動態規劃?這種算法有何神奇之處?本文作者給出了初步的解答。假設你正在使用適當的輸入數據進行一些計算。你在每個實例中都進行了一些計算,以便得到一些結果。當你提供相同的輸入時,你不知道會有相同的輸出。這就像你在重新計算之前已經計算好的特定結果一樣。那麼問題出在哪裡呢?
  • 動態規划算法入門,這就夠了
    動態規劃(Dynamic programming,簡稱DP),是大家都覺得比較難以掌握的算法。為了應付面試,我們經常會背誦一下斐波那楔數列或者背包問題的源碼,其實,只要理解了思想,掌握基本的模型,然後再來點寫代碼的套路,動態規劃並沒有那麼難。
  • 計算機五大算法之三,動態規劃
    與分治法不同的是,動態規劃求解的問題,子問題往往不是相互獨立的,若用分治法解這類問題,分解得到的子問題太多,有些子問題,重複計算很多次。如果能保存已解決子問題的解,需要時找出已求解的解,就可以避免大量重複計算,所以可以用一個表來記錄已求解子問題的解,這就是動態規劃的基本思路。二、適用情況和基本特徵適用動態規划算法必須滿足最優化原理、無後效性和子問題重疊性1.
  • 動態規划算法Dynamic Programming
    動態規劃與分治法相似,都是通過組合子問題的解來求解原問題。不同的是,分治法將問題劃分為互不相交的子問題,遞歸的求解子問題,再將他們的解組合起來,求出原問題的解。與之相反,動態規劃應用於子問題重疊的情況,即不同的子問題具有公共的子子問題。在這種情況下,分治法會做許多不必要的工作,他會反覆求解那些公共子子問題。
  • 如何掌握動態規划算法的套路? - 51CTO.COM
    實際上,它並不是不是一種確定的算法,它是一種最優化的方法求解問題的思想或方法。它是由美國數學家貝爾曼(Bellman)在研究多階段決策過程的優化問題時提出。不過,與之對應的還有一些與時間無關的靜態規劃,如:線性規劃、非線性規劃等。在運籌學中,動態規劃是的非常重要的內容,在各個行業領域都有著廣泛的應用。我們如何理解動態規劃?
  • 豁然開朗經典算法之「動態規劃」
    《算法導論》是這樣解釋動態規劃的:動態規劃與分治法相似,都是通過組合子問題的解來求解原問題答案,將問題劃分為互不相交的子問題,遞歸的求解子問題,最後合併子問題的答案,得到原問題的答案。翻譯成人話就是:計算並存儲小問題的解,並將這些解組合成大問題的解。
  • 動態規划算法幫我通關了「魔塔」
    現在你是一名騎士,將會出現在最上角,公主被困在最右下角,你只能向右和向下移動,請問騎士的初始生命值至少為多少,才能成功救出公主?換句話說,就是問你至少需要多少初始生命值,能夠讓騎士從最左上角移動到最右下角,且任何時候生命值都要大於 0。
  • 快速搞定動態規劃
    概念:動態規劃(dynamic programming)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法。一個主問題往往要重複計算很多次子問題,如果能夠將這些子問題的答案記錄下來,需要的時候再找出之前已經求出的答案,就可以避免大量的重複計算.我們可以用一個表來記錄所有已解的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。具體的動態規划算法多種多樣,但它們具有相同的填表格式。
  • 算法之動態規劃實戰
    動態規劃是一種在數學,計算機科學及經濟學中使用的,通過把原問題分解為相對簡單的子問題的方式求解複雜問題的方法。動態規劃背後的思想非常簡單,基本思想是這樣的,給定一個要求解的問題,我們需要解其不同部分也就是子問題,然後在把這些子問題進行合併,最後得到原問題的解。
  • 關於動態規劃,你想知道的都在這裡了
    你在面試的過程中也很可能會被要求解決這樣的問題,因此,了解這項技術的重要性自然不言而喻。接下來,我將解釋什麼是動態規劃,給出一個解決動態規劃問題的秘訣,並且和大家一起分析幾個示例,以便你能夠更好地理解其應用場合和應用方法。
  • 計算機解決問題沒有奇技淫巧,但動態規劃還是有點套路
    作者 | labuladong【導讀】動態規划算法似乎是一種很高深莫測的算法,你會在一些面試或算法書籍的高級技巧部分看到相關內容,什麼狀態轉移方程,重疊子問題,最優子結構等高大上的詞彙也可能讓你望而卻步。而且,當你去看用動態規劃解決某個問題的代碼時,你會覺得這樣解決問題竟然如此巧妙,但卻難以理解,你可能驚訝於人家是怎麼想到這種解法的。
  • 你不得不看的最重要的算法類型
    算法: 算法是解決問題的分步過程。一個好的算法應該在時間和空間上進行優化。不同類型的問題需要以最優化的方式解決不同類型的算法技術。世界上有很多類型的算法,但是本文將討論您必須知道的最重要和最基本的算法。
  • 算法思想簡介(分制),貪心(DJS)動態分配(dp)回溯(萬能)
    不同點:動態規劃法是將待求解問題分解成若干個相互重疊的子問題,而分治法是分解成若干個互不相交的子問題。利用分治法求解,這些子問題的重疊部分被重複計算多次。而動態規劃法將每個子問題只求解一次並講其保存在一個表格中,當需要再次求解此子問題時,只是簡單地通過查表獲得該子問題的解,從而避免了大量的重複計算。
  • 關於掃地機器人路徑規划算法的解讀
    通常,移動機器人實現路徑規劃需要解決這三大問題:1.機器人從初始位置到目標位置的運動;2.通過相關算法使機器人能夠繞開障礙物,並且經過某些必須經過的地方完成對應的工作任務;3.在完成以上任務的前提下,能做到機器人運動軌跡的優化。
  • Java實現冒泡排序算法
    4.常用數據結構數組、鍊表、棧、隊列 散列表、二叉樹、堆 跳表、圖5.常用算法 遞歸、排序、二分查找 搜索、哈希、貪心、分治 動態規劃、字符串匹配>2.考考你在上一篇:數據結構與算法系列十(排序概述)中,我們列舉了常用的排序算法,以及分析了如何綜合衡量排序算法的優劣。
  • Github標星42K+超火的幾份算法筆記寶典,刷新了我對算法的新認知
    另外,除應付面試之外,還有很重要的一點, 甚至是更重要的一點,就是本筆記可以幫我們打開思路,因為很多算法題的解法是需要逆向思維的,需要跳出原有的固定思維模式,當思維模式被打開之後,你會發現原有的事物現在看起來會有不同的看法,因為角度變了。不過這只能自己體會。
  • 美團技術解析:自動駕駛中的決策規划算法概述
    常見的全局路徑規划算法包括Dijkstra和A算法,以及在這兩種算法基礎上的多種改進。Dijkstra算法[3]和A*算法[4]也是在許多規劃問題中應用最為廣泛的兩種搜索算法。 全局路徑規劃示意 1. Dijkstra算法 Dijkstra算法是由計算機科學家Edsger W. Dijkstra在1956年提出,用來尋找圖形中節點之間的最短路徑。在Dijkstra算法中,需要計算每一個節點距離起點的總移動代價。同時,還需要一個優先隊列結構。對於所有待遍歷的節點,放入優先隊列中會按照代價進行排序。
  • 圖的最短路徑算法-Floyd算法-弗洛伊德算法
    Floyd算法又稱為插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法 在計算機科學中,Floyd-Warshall算法是一種在具有正或負邊緣權重(但沒有負周期)的加權圖中找到最短路徑的算法。算法的單個執行將找到所有頂點對之間的最短路徑的長度(加權)。