日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

动态规划详解

發布時間:2024/3/12 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

以下為最常見的使用動態規劃的例子:

一、動態規劃的三大步驟

動態規劃即利用歷史記錄來避免重復計算。而這些歷史記錄,我們得需要一些變量來保存,一般是用一維數組或者二維數組來保存。下面我們先來講下做動態規劃題很重要的三個步驟:
第一步驟:定義數組元素的含義:我們會用一個數組,來保存歷史數組,假設用一維數組 dp[] 吧。這個時候有一個非常重要的點,就是規定你這個數組元素的含義,例如你的 dp[i] 是代表什么意思?
第二步驟:找出數組元素之間的關系式:我覺得動態規劃,還是有一點類似于高中學習的歸納法,當我們要計算 dp[n] 時,是可以利用 dp[n-1],dp[n-2]……dp[1],來推出 dp[n] 的,也就是可以利用歷史數據來推出新的元素值,所以我們要找出數組元素之間的關系式,例如 dp[n] = dp[n-1] + dp[n-2],這個就是他們的關系式了。而這一步,也是最難的一步,后面我會講幾種類型的題來說。
動態規劃方法的思想是:將原問題分解為若干子問題,稱為「最優子結構」,通過求解子問題完成對最終問題的求解。對于重復出現的子問題,在第一次出現時對其進行求解,然后保存其結果,從而在求解后續的子問題時可以直接利用先前得到的結果。
第三步驟:找出初始值:學過數學歸納法的都知道,雖然我們知道了數組元素之間的關系式,例如 dp[n] = dp[n-1] + dp[n-2],我們可以通過 dp[n-1] 和 dp[n-2] 來計算 dp[n],但是,我們得知道初始值啊,例如一直推下去的話,會由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我們必須要能夠直接獲得 dp[2] 和 dp[1] 的值,而這,就是所謂的初始值。
由了初始值,并且有了數組元素之間的關系式,那么我們就可以得到 dp[n] 的值了,而 dp[n] 的含義是由你來定義的,你想求什么,就定義它是什么,這樣,這道題也就解出來了。

二、案例詳解

案例一、簡單的一維 DP

問題描述:一只青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。

a.定義數組元素的含義

首先我們來定義 dp[i] 的含義,我們的問題是要求青蛙跳上 n 級的臺階總共由多少種跳法,那我們就定義 dp[i] 的含義為:跳上一個 i 級的臺階總共有 dp[i] 種跳法。這樣,如果我們能夠算出 dp[n],不就是我們要求的答案嗎?所以第一步定義完成。

b.找出數組元素間的關系式

我們的目的是求 dp[n]動態規劃的題,如你們經常聽說的那樣,就是把一個規模比較大的問題分成幾個規模比較小的問題,然后由小的問題推導出大的問題。也就是說,dp[n] 的規模為 n,比它規模小的是 n-1, n-2, n-3…. 也就是說,dp[n] 一定會和 dp[n-1], dp[n-2]….存在某種關系的。我們要找出他們的關系。
那么問題來了,怎么找?是最核心最難的一個,我們必須回到問題本身來了,來尋找他們的關系式,dp[n] 究竟會等于什么呢?
對于這道題,由于情況可以選擇跳一級,也可以選擇跳兩級,所以青蛙到達第 n 級的臺階有兩種方式
一種是從第 n-1 級跳上來
一種是從第 n-2 級跳上來
由于我們是要算所有可能的跳法的,所以有 dp[n] = dp[n-1] + dp[n-2]。

c.找出初始條件

當 n = 1 時,dp[1] = dp[0] + dp[-1],而我們是數組是不允許下標為負數的,所以對于 dp[1],我們必須要直接給出它的數值,相當于初始值,顯然,dp[1] = 1。一樣,dp[0] = 0.(因為 0 個臺階,那肯定是 0 種跳法了)。于是得出初始值:dp[0] = 0.

三個步驟都做出來了,那么我們就來寫代碼吧,代碼會詳細注釋滴。

int f( int n ){if(n <= 1)return n;// 先創建一個數組來保存歷史數據int[] dp = new int[n+1];// 給出初始值dp[0] = 0;dp[1] = 1;// 通過關系式來計算出 dp[n]for(int i = 2; i <= n; i++){dp[i] = dp[i-1] + dp[i-2];}// 把最終結果返回return dp[n]; }

d.再說初始化

大家先想以下,你覺得,上面的代碼有沒有問題?
答是有問題的,還是錯的,錯在對初始值的尋找不夠嚴謹。例如對于上面的題,當 n = 2 時,dp[2] = dp[1] + dp[0] = 1。這顯然是錯誤的,你可以模擬一下,應該是 dp[2] = 2。
也就是說,在尋找初始值的時候,一定要注意不要找漏了,dp[2] 也算是一個初始值,不能通過公式計算得出。有人可能會說,我想不到怎么辦?這個很好辦,多做幾道題就可以了。

案例二:二維數組的 DP

可以說,80% 的題都是要用二維數組的,所以下面的題主要以二維數組為主,當然有人可能會說,要用一維還是二維,我怎么知道?這個問題不大,接著往下看。
問題描述:一個機器人位于一個 m*n 網格的左上角 (起始點在下圖中標記為“Start” )。機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記為“Finish”)。問總共有多少條不同的路徑?

步驟一、定義數組元素的含義

由于我們的目的是從左上角到右下角一共有多少種路徑,那我們就定義 dp[i] [j]的含義為:當機器人從左上角走到(i, j) 這個位置時,一共有 dp[i] [j] 種路徑。那么,dp[m-1] [n-1] 就是我們要的答案了。
注意,這個網格相當于一個二維數組,數組是從下標為 0 開始算起的,所以 右下角的位置是(m-1, n - 1),所以 dp[m-1] [n-1]就是我們要找的答案。

步驟二:找出關系數組元素間的關系式

想象以下,機器人要怎么樣才能到達 (i, j) 這個位置?由于機器人可以向下走或者向右走,所以有兩種方式到達
一種是從 (i-1, j) 這個位置走一步到達(是(i,j)正上方的那一格
一種是從(i, j - 1) 這個位置走一步到達(是(i,j)正前方的那一格
因為是計算所有可能的步驟,所以是把所有可能走的路徑都加起來,所以關系式是 dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]。

步驟三、找出初始值

顯然,當 dp[i] [j]中,如果 i 或者 j 有一個為 0,那么還能使用關系式嗎?答是不能的,因為這個時候把 i - 1 或者 j - 1,就變成負數了,數組就會出問題了,所以我們的初始值是計算出所有的dp[0] [0….n-1]和所有的 dp[0….m-1] [0]。這個還是非常容易計算的,相當于計算機圖中的最上面一行和左邊一列。因此初始值如下:
dp[0] [0….n-1] = 1; // 相當于最上面一行,機器人只能一直往左走
dp[0…m-1] [0] = 1; // 相當于最左面一列,機器人只能一直往下走

public static int uniquePaths(int m, int n) {if (m <= 0 || n <= 0) {return 0;}int[][] dp = new int[m][n]; // // 初始化for(int i = 0; i < m; i++){dp[i][0] = 1;}for(int i = 0; i < n; i++){dp[0][i] = 1;}// 推導出 dp[m-1][n-1]for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {dp[i][j] = dp[i-1][j] + dp[i][j-1];}}return dp[m-1][n-1]; }

案例三、二維數組 DP

問題描述
給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。
說明:每次只能向下或者向右移動一步。
舉例:
輸入:
arr = [
[1,3,1],
[1,5,1],
[4,2,1]
]
輸出: 7
解釋: 因為路徑 1→3→1→1→1 的總和最小。
和上面的差不多,不過是算最優路徑和.
leetcode 的第64題:https://leetcode-cn.com/problems/minimum-path-sum/
這些屬于 medium 級別的,后面在給幾道 hard 級別的。

步驟一、定義數組元素的含義

由于我們的目的是從左上角到右下角,最小路徑和是多少,那我們就定義 dp[i] [j]的含義為:當機器人從左上角走到(i, j) 這個位置時,最下的路徑和是 dp[i] [j]。那么,dp[m-1] [n-1]就是我們要的答案了。
注意,這個網格相當于一個二維數組,數組是從下標為 0 開始算起的,所以 由下角的位置是(m-1, n - 1),所以dp[m-1] [n-1]就是我們要走的答案。

步驟二:找出關系數組元素間的關系式

想象以下,機器人要怎么樣才能到達 (i, j) 這個位置?由于機器人可以向下走或者向右走,所以有兩種方式到達
一種是從 (i-1, j) 這個位置走一步到達
一種是從(i, j - 1) 這個位置走一步到達
不過這次不是計算所有可能路徑,而是計算哪一個路徑和是最小的,那么我們要從這兩種方式中,選擇一種,使得dp[i] [j] 的值是最小的,顯然有
dp[i] [j] = min(dp[i-1][j],dp[i][j-1]) + arr[i][j];// arr[i][j] 表示網格種的值

步驟三、找出初始值

顯然,當 dp[i] [j]中,如果 i 或者 j 有一個為 0,那么還能使用關系式嗎?答是不能的,因為這個時候把 i - 1 或者 j - 1,就變成負數了,數組就會出問題了,所以我們的初始值是計算出所有的 dp[0] [0….n-1]和所有的dp[0….m-1] [0]。這個還是非常容易計算的,相當于計算機圖中的最上面一行和左邊一列。因此初始值如下:
dp[0] [j] = arr[0] [j] + dp[0] [j-1]; // 相當于最上面一行,機器人只能一直往左走
dp[i] [0] = arr[i] [0] + dp[i-1] [0]; // 相當于最左面一列,機器人只能一直往下走

public static int uniquePaths(int[][] arr) {int m = arr.length;int n = arr[0].length;if (m <= 0 || n <= 0) {return 0;}int[][] dp = new int[m][n]; // // 初始化dp[0][0] = arr[0][0];// 初始化最左邊的列for(int i = 1; i < m; i++){dp[i][0] = dp[i-1][0] + arr[i][0];}// 初始化最上邊的行for(int i = 1; i < n; i++){dp[0][i] = dp[0][i-1] + arr[0][i];}// 推導出 dp[m-1][n-1]for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + arr[i][j];}}return dp[m-1][n-1]; } class Solution { public:/** @param matrix int整型vector<vector<>> the matrix* @return int整型*/int minPathSum(vector<vector<int> >& matrix) {int row = matrix.size(), col = matrix[0].size(); for (int i = 0; i < row; i ++) {for (int j = 0; j < col; j ++) {if (i == 0 && j != 0) // 數組第一行{matrix[0][j] += matrix[0][j - 1];} else if (j == 0 && i != 0) // 數組第一列{ matrix[i][0] += matrix[i - 1][0]; } else if (i > 0 && j > 0) // 兩種方式取最小的{ matrix[i][j] += min(matrix[i - 1][j], matrix[i][j - 1]);}}}return matrix[row - 1][col - 1];} };

案例 4:編輯距離

這次給的這道題比上面的難一些,在 leetcdoe 的定位是 hard 級別。好像是 leetcode 的第 72 號題。
問題描述
給定兩個單詞 word1 和 word2,計算出將 word1 轉換成 word2 所使用的最少操作數 。你可以對一個單詞進行如下三種操作:

a. 插入一個字符
示例 1: 輸入: word1 = "horse", word2 = "ros" 輸出: 3 解釋: horse -> rorse ('h' 替換為 'r') rorse -> rose (刪除 'r') rose -> ros (刪除 'e')

90% 的字符串問題都可以用動態規劃解決,并且90%是采用二維數組。

步驟一、定義數組元素的含義

由于我們的目的求將 word1 轉換成 word2 所使用的最少操作數 。那我們就定義 dp[i] [j]的含義為:當字符串 word1 的長度為 i,字符串 word2 的長度為 j 時,將 word1 轉化為 word2 所使用的最少操作次數為 dp[i] [j]。
步驟二:找出關系數組元素間的關系式
接下來我們就要找 dp[i] [j]元素之間的關系了,比起其他題,這道題相對比較難找一點,但是,不管多難找,大部分情況下,dp[i] [j]和 dp[i-1] [j]、dp[i] [j-1]、dp[i-1] [j-1] 肯定存在某種關系。因為我們的目標就是,從規模小的,通過一些操作,推導出規模大的。對于這道題,我們可以對 word1 進行三種操作
插入一個字符
由于我們是要讓操作的次數最小,所以我們要尋找最佳操作。那么有如下關系式:

  • 如果我們 word1[i] 與 word2 [j] 相等,這個時候不需要進行任何操作,顯然有 dp[i] [j] = dp[i-1] [j-1]。(別忘了 dp[i] [j] 的含義哈)。

  • 如果我們 word1[i] 與 word2 [j] 不相等,這個時候我們就必須進行調整,而調整的操作有 3 種,我們要選擇一種。三種操作對應的關系試如下(注意字符串與字符的區別):

    I. 如果把字符 word1[i] 替換成與 word2[j] 相等,則有 dp[i] [j] = dp[i-1] [j-1] + 1;

    II. 如果在字符串 word1末尾插入一個與 word2[j] 相等的字符,則有 dp[i] [j] = dp[i] [j-1] + 1;

    III.如果把字符 word1[i] 刪除,則有 dp[i] [j] = dp[i-1] [j] + 1;

  • 那么我們應該選擇一種操作,使得 dp[i] [j] 的值最小,顯然有
    dp[i] [j] = min(dp[i-1] [j-1],dp[i] [j-1],dp[[i-1] [j]]) + 1;
    于是,我們的關系式就推出來了,

    步驟三、找出初始值

    顯然,當 dp[i] [j] 中,如果 i 或者 j 有一個為 0,那么還能使用關系式嗎?答是不能的,因為這個時候把 i - 1 或者 j - 1,就變成負數了,數組就會出問題了,所以我們的初始值是計算出所有的 dp[0] [0….n] 和所有的 dp[0….m] [0]。這個還是非常容易計算的,因為當有一個字符串的長度為 0 時,轉化為另外一個字符串,那就只能一直進行插入或者刪除操作了。

    public int minDistance(String word1, String word2) {int n1 = word1.length();int n2 = word2.length();int[][] dp = new int[n1 + 1][n2 + 1];// dp[0][0...n2]的初始值for (int j = 1; j <= n2; j++) dp[0][j] = dp[0][j - 1] + 1;// dp[0...n1][0] 的初始值for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;// 通過公式推出 dp[n1][n2]for (int i = 1; i <= n1; i++) {for (int j = 1; j <= n2; j++) {// 如果 word1[i] 與 word2[j] 相等。第 i 個字符對應下標是 i-1if (word1.charAt(i - 1) == word2.charAt(j - 1)){p[i][j] = dp[i - 1][j - 1];}else {dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;} }}return dp[n1][n2]; }

    最后說下,如果你要練習,可以去 leetcode,選擇動態規劃專題,然后連續刷幾十道,保證你以后再也不怕動態規劃了。當然,遇到很難的,咱還是得掛。
    Leetcode 動態規劃直達:https://leetcode-cn.com/tag/dynamic-programming/

    總結

    以上是生活随笔為你收集整理的动态规划详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。