前端面試--什麼是動態規劃?

前言

最近作了一些leetcode的動態規劃的算法題,原本我一個小小菜雞是不配來寫這個東西的,可是也壯着膽子來寫一篇本身對於動態規劃的理解和作題的思路,望各路大佬留情。算法

什麼是動態規劃

引用百度百科的一句話:數組

動態規劃算法一般用於求解具備某種最優性質的問題。在這類問題中,可能會有許多可行解。每個解都對應於一個值,咱們但願找到具備最優值的解。markdown

動態規劃是一種思想,與分治思想很相似網絡

分治問題的核心思想是:把一個問題分解爲相互獨立的子問題,逐個解決子問題後,再組合子問題的答案,就獲得了問題的最終解。函數

動態規劃的思想和「分治」有點類似。不一樣之處在於,「分治」思想中,各個子問題之間是獨立的:而動態規劃劃分出的子問題,每每是相互依賴、相互影響的。優化

何時該用動態規劃

引用修言大佬的一句話ui

  1. 最優子結構 2.重疊子問題

最優子結構它指的是問題的最優解包含着子問題的最優解——無論前面的決策如何,此後的狀態必須是基於當前狀態(由上次決策產生)的最優決策。而重疊子問題,它指的是在遞歸的過程當中,出現了反覆計算的狀況。this

作題

首先上一道leetcode簡單題:303. 區域和檢索 - 數組不可變spa

示例:prototype

輸入:
["NumArray""sumRange""sumRange""sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
輸出:
[null, 1, -1, -3]

解釋:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1)) 
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))

來源:力扣(LeetCode)
連接:https://leetcode-cn.com/problems/range-sum-query-immutable
著做權歸領釦網絡全部。商業轉載請聯繫官方受權,非商業轉載請註明出處。
複製代碼

這題爲何定義爲簡單呢,你們能夠想一下若是這題用暴力解法,那麼咱們很容易獲得結論

var NumArray = function (nums) {
    this.nums = nums
};
NumArray.prototype.sumRange = function (i, j) {
    const arr = this.nums.slice(i, j + 1)
    return arr.reduce((a, c) => a + c)
};
複製代碼

我給你們跑一下運行結果:

結果非常慘淡,執行用時1064ms,內存消耗47.1MB。爲何呢?由示例咱們能夠看到,sumRange的這個函數是會屢次調用的,而咱們在裏面評分的切割數組,而且使用reduce去求和,這個消耗無疑是很大的。

那還有什麼解決方法呢? 動態規劃能夠嗎?能夠!

思路:爲了不上面的屢次調用的狀況,咱們能夠把該作的操做在new的時候一步到位,在調用sumRange的函數的直接返回就行。

那到底該怎麼用動態規劃來優化呢?

個人思路是,在new這個函數的時候在函數內部維護一個dp數組dp數組內存儲傳入數組nums的當前項nums[i]與i以前全部項的和。這樣咱們在調用sumRange函數時只須要返回dp[j]-dp[i]

new函數代碼以下:

var NumArray = function (nums) {
    this.dp = []
    const dp = this.dp
    dp[0] = nums[0]
    for (let i = 1; i < nums.length; i++) {
        dp[i] = dp[i - 1] + nums[i]
    }
};
複製代碼

此時咱們就維護好了dp數組,接下來就很簡單了

   NumArray.prototype.sumRange = function (i, j) {
    const dp = this.dp;
    if (i === 0) return dp[j]
    return dp[j] - dp[i - 1]
};
複製代碼

爲何要加一個i===0的判斷?

由於咱們在i=0的時候,就至關於拿前j項的和,而且此時時沒有i-1項的。當i>1時,數學你們都學的比我好,很容易得出dp[j] - dp[i - 1]

咱們來跑下結果

完美!

第二題:leetcode中等題:198. 打家劫舍

題目:

示例:

輸入:[1,2,3,1]
輸出:4
解釋:偷竊 1 號房屋 (金額 = 1) ,而後偷竊 3 號房屋 (金額 = 3)。
     偷竊到的最高金額 = 1 + 3 = 4 。

來源:力扣(LeetCode)
連接:https://leetcode-cn.com/problems/house-robber
著做權歸領釦網絡全部。商業轉載請聯繫官方受權,非商業轉載請註明出處。
複製代碼

這題目簡單的理解就是,規則是不能取數組相鄰項,返回規則下的最大值

這題咱們主要考慮兩種狀況(對於第i家):

    1. 偷:若是咱們偷了第i家,那麼咱們確定不能偷第i-1家,那麼此時咱們的最大值就是第i家的錢加上咱們i-2時偷到的最大值。
    1. 不偷:若是咱們不偷第i家,那麼此時咱們偷到錢的最大值,就是在第i-1家時偷到的最大值。

對於示例的數組,咱們畫圖分析: 對於第一家,第一家比較窮,只有1塊錢,全部咱們第一家偷到的最多錢就是1塊錢,對應的腦圖和dp數組爲:

第二家,中層領導,有2塊錢,此時咱們有兩種方案:

    1. 偷第二家,此時咱們就不能偷第一家,由於他們相鄰,都偷會引來警察叔叔,因此這時候咱們只偷第二家。對於腦圖和dp數組爲:

此時咱們能偷到的最多錢爲2

    1. 不偷,有錢我不偷,哎就是玩!那麼此時咱們的腦圖和dp數組維持第一家的狀況

顯然 偷第二家比較划算 此時咱們dp[1]=2;

此時,偷到第二家能偷到的最多錢咱們很容易獲得Math.max(dp[0],dp[1]) 也就是等於dp[1],也就是2塊錢

對於第三家:高層領導,有3塊錢,咱們依然有兩種方案:

    1. 偷第三家,此時咱們就不能偷第二家,因此這時候咱們能偷到的最大錢數就是第三家的錢跟在第一家的時候偷到的錢的總和。對於腦圖和dp數組爲:
    1. 不偷第三家,那咱們能偷到的最多錢就是在第二家偷到的最多錢:

此時咱們用Math.max(dp[1]+nums[3],dp[2])取出最大值爲4,這就是咱們第三家能偷到的最多錢。

對於第四家,一樣的道理,你們能夠本身理一下。這裏我就直接上代碼了

var rob = function (nums) {
    const len = nums.length
    if (len === 0) return 0;
    if (len === 1) return nums[0]
    if (len === 2) return Math.max(...nums)
    const dp = []
    dp[0] = nums[0]
    dp[1] = Math.max(nums[0], nums[1])
    for (let i = 2; i < len; i++) {
        dp[i] = Math.max((nums[i] + dp[i - 2]), dp[i - 1])
    }
    return dp[dp.length - 1]
};
複製代碼

最後跑一下運行結果:

完美經過!

總結

個人理解爲:咱們在遇到一些題目的題解跟拆分出來的每一項小問題有關時,能夠考慮使用動態規劃的解題思路,能夠清晰的理清題目的邏輯,幫助咱們快速找到答案!

因爲本人技術有限,文內若有錯誤,敬請與我聯繫,謝謝!

相關文章
相關標籤/搜索