【算法精研】動態規划 總集篇


shadow-logo

每當談及 動態規划,就會被問到 如下問題:

什么是 動態規划

概念

動態規划Dynamic Programming,簡稱“DP”)
是運籌學的一個分支,是求解決策過程最優化的過程。
20世紀50年代初,美國數學家貝爾曼(R.Bellman)等人在研究多階段決策過程的優化問題時,提出了著名的最優化原理,從而創立了動態規划。
動態規划的應用極其廣泛,包括工程技術、經濟、工業生產、軍事以及自動化控制等領域,並在背包問題、生產經營問題、資金管理問題、資源分配問題、最短路徑問題和復雜系統可靠性問題等中取得了顯著的效果

(摘自 百度百科)


動態規划 的原理是什么?

原理

在現實生活中,有一類活動的過程,
由於它的特殊性,可將過程分成若干個互相聯系的階段
在它的每一階段都需要作出決策,從而使 整個過程 達到 最好 的活動效果
因此各個階段決策的選取不能任意確定,
依賴於當前面臨的狀態,又 影響以后的發展
當各個階段決策確定后,就組成一個 決策序列,因而也就確定了整個過程的一條活動路線.
這種把一個問題看作是一個 前后關聯、具有鏈狀結構多階段過程就稱為 多階段決策過程,這種問題稱為 多階段決策問題
在多階段決策問題中,各個階段采取的決策,一般來說是 與時間有關的,決策依賴於當前狀態,又隨即引起狀態的轉移
一個決策序列就是在 變化的狀態 中產生出來的,故有“動態”的含義,
稱這種解決 多階段決策最優化 的過程為 動態規划 方法

(摘自 百度百科)

那么,根據上述的講解,本人來用一句話概括:

動態規划 就是 計算 多階段問題的全局最優解遞推算法


動態規划 和 貪心 的區別是什么?

區別

動態規划 貪心算法
順序 自底向上 自頂向下
性質 全局最優解 局部最優解

相信很多同學都見過如上兩條區別,但是能徹底理解的確是不多

那么,本人現在來講解下上述兩點區別:

順序角度:

貪心算法:

貪心算法中,作出的 每步貪心決策無法改變
因為貪心策略是由上一步的最優解 推導 下一步的最優解
上一步之前的最優解不作保留

動態規划:

全局最優解 中一定包含 某個局部最優解,但不一定包含前一個局部最優解,因此需要記錄之前的所有最優解

性質角度:

貪心算法:

貪心算法中,是根據之前的條件,來推導 當前最優結果

動態規划:

動態規划 則是 根據 之后的情況,來推導 當前最優結果


動態規划 有哪些題型呢?

斐波那契數列
矩陣路徑
數組區間
分割整數
最長遞增序列
最長公共子序列
0-1背包
股票交易
字符串編輯


那么,介紹了這么多 冷冰冰 的概念,
接下來,本人就來通過些許例題,來帶同學們了解並鞏固下 動態規划 吧!

斐波那契數列 系列:

斐波那契數列 問題:

題目描述:

寫一個函數,輸入 n ,求斐波那契(Fibonacci)數列的第 n 項。斐波那契數列的定義如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1

斐波那契數列由 0 和 1 開始,之后的斐波那契數就是由之前的兩數相加而得出。
答案需要取模 1e9+7(1000000007),如計算初始結果為:1000000008,請返回 1。

示例:

示例1

輸入:n = 2
輸出:1

示例2

n = 5
5

提示:

0 <= n <= 100

題解:

題解鏈接


母牛繁殖 問題:

題目描述:

假設農場中成熟的母牛每年都會生 1 頭小母牛,並且永遠不會死。
第一年有 1 只小母牛,從第二年開始,母牛開始生小母牛。
每只小母牛 3 年之后成熟又可以生小母牛。
給定整數 n,求 n 年后牛的數量。

題解:

public class Solution {

    public int rob(int n) {
        if (n <= 0) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        if (n == 3) {
            return 4;
        }

        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 4;
        for (int i = 2; i < n; i++) {
            dp[i] = dp[i - 1] + dp[i - 3];
        }
        return dp[n];
    }

}

爬樓梯 問題:

題目描述:

假設你正在爬樓梯。
需要 n 階你才能到達樓頂。
每次你可以爬 1 或 2 個台階。
你有多少種不同的方法可以爬到樓頂呢?

注意:

給定 n 是一個正整數。

示例:

示例 1

輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。

  1. 1 階 + 1 階
  2. 2 階

示例 2

輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。

  1. 1 階 + 1 階 + 1 階
  2. 1 階 + 2 階
  3. 2 階 + 1 階

題解:

題解鏈接


強盜搶劫Ⅰ—— 串形房屋:

題目描述:

你是一個專業的小偷,計划偷竊沿街的房屋。
每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,
給定一個代表每個房屋存放金額的非負整數數組,計算你 不觸動警報裝置的情況下 ,一夜之內能夠偷竊到的最高金額

示例:

示例 1

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

示例 2

輸入:[2,7,9,3,1]
輸出:12
解釋: 偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接着偷竊 5 號房屋 (金額 = 1)。
偷竊到的最高金額 = 2 + 9 + 1 = 12 。

提示:

1、0 <= nums.length <= 100
2、0 <= nums[i] <= 400

題解:

題解鏈接


強盜搶劫Ⅱ—— 環形房屋:

題目描述:

你是一個專業的小偷,計划偷竊沿街的房屋。
這個地方所有的房屋都 圍成一圈 ,這意味着第一個房屋和最后一個房屋是緊挨着的。
同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警 。
給定一個代表每個房屋存放金額的非負整數數組,計算你 在不觸動警報裝置的情況下 ,能夠偷竊到的最高金額

示例:

示例 1

輸入:nums = [1,2,3,1]
輸出:4
解釋:你可以先偷竊 1 號房屋(金額 = 1),然后偷竊 3 號房屋(金額 = 3)。
偷竊到的最高金額 = 1 + 3 = 4 。

示例 2

輸入:nums = [2,3,2]
輸出:3
解釋:你不能先偷竊 1 號房屋(金額 = 2),然后偷竊 3 號房屋(金額 = 2), 因為他們是相鄰的。

示例 3

輸入:nums = [0]
輸出:0

提示:

1、1 <= nums.length <= 100
2、0 <= nums[i] <= 1000

題解:

題解鏈接


矩陣路徑 系列:

最小路徑和 問題:

題目描述:

給定一個包含非負整數的 m x n 網格 grid ,
請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。

說明:

每次只能向下或者向右移動一步。

注意:

給定 n 是一個正整數。

示例:

示例 1
圖示

輸入:grid = [[1,3,1],[1,5,1],[4,2,1]]
輸出:7
解釋:因為路徑 1→3→1→1→1 的總和最小。

示例 2

輸入:grid = [[1,2,3],[4,5,6]]
輸出:12

提示:

1、m == grid.length
2、n == grid[i].length
3、1 <= m, n <= 200
4、0 <= grid[i][j] <= 100

題解:

題解鏈接


不同路徑:

題目描述:

一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記為“Start” )。
機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記為“Finish”)。
問總共有多少條不同的路徑?

圖示

示例:

示例 1

輸入:m = 3, n = 2
輸出:3
解釋:從左上角開始,總共有 3 條路徑可以到達右下角:

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右

示例 2

輸入:m = 7, n = 3
輸出:28

提示:

1、1 <= m, n <= 100
2、題目數據保證答案小於等於 2 * 10 ^ 9

題解:

題解鏈接


數組區間 問題:

區域和檢索 - 數組不可變:

題目描述:

給定一個整數數組 nums,求出數組從索引 i 到 j(i ≤ j)范圍內元素的總和,包含 i、j 兩點。
實現 NumArray 類:

1.NumArray(int[] nums) 使用數組 nums 初始化對象
2. int sumRange(int i, int j) 返回數組 nums 從索引 i 到 j(i ≤ j)范圍內元素的總和,
包含 i、j 兩點(也就是 sum(nums[i], nums[i + 1], ... , nums[j]))

示例:

示例 1

輸入:

["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))

提示:

1、0 <= nums.length <= 104
2、-105 <= nums[i] <= 105
3、0 <= i <= j < nums.length
4、最多調用 104 次 sumRange 方法

題解:

題解鏈接


等差遞增子區間:

題目描述:

如果一個數列至少有三個元素,並且任意兩個相鄰元素之差相同,則稱該數列為等差數列。
例如,以下數列為等差數列:

1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9

以下數列不是等差數列。

1, 1, 2, 5, 7

數組 A 包含 N 個數,且索引從0開始。數組 A 的一個子數組划分為數組 (P, Q),P 與 Q 是整數且滿足 0<=P<Q<N 。
如果滿足以下條件,則稱子數組(P, Q)為等差數組:

元素 A[P], A[p + 1], ..., A[Q - 1], A[Q] 是等差的。並且 P + 1 < Q 。

函數要返回數組 A 中所有為等差數組的子數組個數。

示例:

示例 1

A = [1, 2, 3, 4]
返回: 3, A 中有三個子等差數組: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。

題解:

題解鏈接


分割數組 系列:

解碼方法:

題目描述:

一條包含字母 A-Z 的消息通過以下方式進行了編碼:
'A' -> 1
'B' -> 2
...
'Z' -> 26
給定一個只包含數字的非空字符串,請計算解碼方法的總數。
題目數據保證答案肯定是一個 32 位的整數。

示例:

示例 1

輸入:s = "12"
輸出:2
解釋:它可以解碼為 "AB"(1 2)或者 "L"(12)。

示例 2

輸入:s = "226"
輸出:3
解釋:它可以解碼為 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

示例 3

輸入:s = "0"
輸出:0

示例 4

輸入:s = "1"
輸出:1

示例 5

輸入:s = "2"
輸出:1

提示:

  1. 1 <= s.length <= 100
  2. s 只包含數字,並且可能包含前導零。

題解:

題解鏈接


完全平方數:

題目描述:

給定正整數 n,找到若干個完全平方數(比如 1, 4, 9, 16, ...)使得它們的和等於 n。
你需要讓組成和的完全平方數的個數最少。

示例:

示例 1

輸入: n = 12
輸出: 3
解釋: 12 = 4 + 4 + 4.

示例 2

輸入: n = 13
輸出: 2
解釋: 13 = 4 + 9.

題解:

題解鏈接


整數拆分:

題目描述:

給定一個正整數 n,將其拆分為至少兩個正整數的和,並使這些整數的乘積最大化。
返回你可以獲得的最大乘積。

示例:

示例 1

輸入: 2
輸出: 1
解釋: 2 = 1 + 1, 1 × 1 = 1。

示例 2

輸入: 10
輸出: 36
解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

說明:

你可以假設 n 不小於 2 且不大於 58。

題解:

題解鏈接


最長子序列 系列:

最長上升子序列:

題目描述:

給定一個無序的整數數組,找到其中最長上升子序列的長度。

示例:

輸入: [10,9,2,5,3,7,101,18]
輸出: 4
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。

說明:

可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
你算法的時間復雜度應該為 O(n2) 。

進階:

你能將算法的時間復雜度降低到 O(n log n) 嗎?

題解:

題解鏈接


最長數對鏈:

題目描述:

給出 n 個數對。 在每一個數對中,第一個數字總是比第二個數字小。
現在,我們定義一種跟隨關系,當且僅當 b < c 時,數對(c, d) 才可以跟在 (a, b) 后面。
我們用這種形式來構造一個數對鏈。
給定一個數對集合,找出能夠形成的最長數對鏈的長度。
你不需要用到所有的數對,你可以以任何順序選擇其中的一些數對來構造。

示例:

輸入:[[1,2], [2,3], [3,4]]
輸出:2
解釋:最長的數對鏈是 [1,2] -> [3,4]

說明:

給出數對的個數在 [1, 1000] 范圍內。

題解:

題解鏈接


最長擺動子序列:

題目描述:

如果連續數字之間的差嚴格地在正數和負數之間交替,則數字序列稱為擺動序列。
第一個差(如果存在的話)可能是正數或負數。少於兩個元素的序列也是擺動序列。
例如, [1,7,4,9,2,5] 是一個擺動序列,因為差值 (6,-3,5,-7,3) 是正負交替出現的。
相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是擺動序列,第一個序列是因為它的前兩個差值都是正數,第二個序列是因為它的最后一個差值為零。
給定一個整數序列,返回作為擺動序列的最長子序列的長度。
通過從原始序列中刪除一些(也可以不刪除)元素來獲得子序列,剩下的元素保持其原始順序。

示例:

示例 1

輸入: [1,7,4,9,2,5]
輸出: 6
解釋: 整個序列均為擺動序列。

示例 2

輸入: [1,17,5,10,13,15,10,5,16,8]
輸出: 7
解釋: 這個序列包含幾個長度為 7 擺動序列,其中一個可為[1,17,10,13,10,16,8]。

示例 3:

輸入: [1,2,3,4,5,6,7,8,9]
輸出: 2

進階:

你能否用 O(n) 時間復雜度完成此題?

題解:

題解鏈接


最長公共子序列 系列:

最長公共子序列:

題目描述:

給定兩個字符串 text1 和 text2,返回這兩個字符串的最長公共子序列的長度。
一個字符串的子序列是指這樣一個新的字符串:

它是由原字符串在不改變字符的相對順序的情況下刪除某些字符(也可以不刪除任何字符)后組成的新字符串。

例如:

"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
兩個字符串的「公共子序列」是這兩個字符串所共同擁有的子序列。

若這兩個字符串沒有公共子序列,則返回 0。

示例:

示例 1

輸入:text1 = "abcde", text2 = "ace"
輸出:3
解釋:最長公共子序列是 "ace",它的長度為 3。

示例 2

輸入:text1 = "abc", text2 = "abc"
輸出:3
解釋:最長公共子序列是 "abc",它的長度為 3。

示例 3

輸入:text1 = "abc", text2 = "def"
輸出:0
解釋:兩個字符串沒有公共子序列,返回 0。

提示:

  1. 1 <= text1.length <= 1000
  2. 1 <= text2.length <= 1000
  3. 輸入的字符串只含有小寫英文字符。

題解:

題解鏈接


0/1背包 系列:

划分數組為和相等的兩部分:

題目描述:

給定一個只包含正整數的非空數組。
是否可以將這個數組分割成兩個子集,使得兩個子集的元素和相等。

注意:

每個數組中的元素不會超過 100
數組的大小不會超過 200

示例:

示例 1

輸入: [1, 5, 11, 5]
輸出: true
解釋: 數組可以分割成 [1, 5, 5] 和 [11].

示例 2

輸入: [1, 2, 3, 5]
輸出: false
解釋: 數組不能分割成兩個元素和相等的子集.

題解:

題解鏈接


目標和:

題目描述:

給定一個非負整數數組,a1, a2, ..., an, 和一個目標數,S。
現在你有兩個符號+和-。
對於數組中的任意一個整數,你都可以從+或-中選擇一個符號添加在前面。
返回可以使最終數組和為目標數 S 的所有添加符號的方法數。

示例:

輸入:nums: [1, 1, 1, 1, 1], S: 3
輸出:5
解釋:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5種方法讓最終目標和為3。

提示:

1、數組非空,且長度不會超過 20 。
2、初始的數組的和不會超過 1000 。
3、保證返回的最終結果能被 32 位整數存下。

題解:

題解鏈接


一和零:

題目描述:

給你一個二進制字符串數組 strs 和兩個整數 m 和 n 。
請你找出並返回 strs 的最大子集的大小,該子集中 最多 有 m 個 0 和 n 個 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例:

示例 1

輸入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
輸出:4
解釋:最多有 5 個 0 和 3 個 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他滿足題意但較小的子集包括 {"0001","1"} 和 {"10","1","0"} 。
{"111001"} 不滿足題意,因為它含 4 個 1 ,大於 n 的值 3 。

示例 2

輸入:strs = ["10", "0", "1"], m = 1, n = 1
輸出:2
解釋:最大的子集是 {"0", "1"} ,所以答案是 2 。

提示:

1、1 <= strs.length <= 600
2、1 <= strs[i].length <= 100
3、strs[i]僅由'0' 和'1' 組成
4、1 <= m, n <= 100

題解:

題解鏈接


股票交易 系列:

最佳買賣股票時機含冷凍期:

題目描述:

給定一個整數數組,其中第 i 個元素代表了第 i 天的股票價格 。
設計一個算法計算出最大利潤。
在滿足以下約束條件下,你可以盡可能地完成更多的交易(多次買賣一支股票):

  • 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
  • 賣出股票后,你無法在第二天買入股票 (即冷凍期為 1 天)。

示例:

輸入: [1,2,3,0,2]
輸出: 3
解釋: 對應的交易狀態為: [買入, 賣出, 冷凍期, 買入, 賣出]

題解:

題解鏈接


字符串編輯 系列:

兩個字符串的刪除操作:

題目描述:

給定兩個單詞 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步數,
每步可以刪除任意一個字符串中的一個字符。

示例:

輸入: "sea", "eat"
輸出: 2
解釋: 第一步將"sea"變為"ea",第二步將"eat"變為"ea"

提示:

1、給定單詞的長度不超過500。
2、給定單詞中的字符只含有小寫字母。

題解:

題解鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM