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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

动态规划(浅层基础)

發布時間:2025/3/21 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划(浅层基础) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

在我看來動態規劃和遞歸有很大的相似性,可以說動態規劃就是剪枝后的遞歸,它舍去了遞歸重復的步驟,在時間復雜度上相對于遞歸有著非常大的提升,就比如說斐波那契數列(下題有)用遞歸的時間復雜度是指數級,而用動態規劃則可以將其優化為線性級,且動態規劃還可以繼續進行優化使其空間復雜度大大減小,這就是動態規劃進階的內容了;
做動態規劃類的題目有很多相似的套路和方法,開始肯定會有一些困難,題目見多了就會發現好多題有著相似的解法;

我的一般做題步驟是:
1,先找最后一步并定義一個dp[i]數組元素的含義(不一定是數組由題目而定)
2,找出關系數組間的關系式:由最后一步向前推一步,想通過哪一步(或幾步)可以走到最后一步,通過這點,找到數組元素間的關系式(轉移方程)
3,找出初始值(轉移方程算不出的):通過轉移方程來判斷,有沒有特殊情況無法使用轉移方程,單獨列出來,這就是初始值;
ps:這個方法不一定適用于所有題,一定要就題而論;

什么樣的題適合動態規劃求解呢?我總結有以下幾種:
1,求最值類
2,求幾種方法類(計數)
3,判斷是否存在滿足某個條件(一般布爾類型)

下面來幾道有點人氣的題目

斐波那契數

斐波那契數,通常用 F(n) 表示,形成的序列稱為 斐波那契數列 。該數列由 0 和 1 開始,后面的每一項數字都是前面兩項數字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
給你 n ,請計算 F(n) 。

示例 1:
輸入:2
輸出:1
解釋:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:
輸入:3
輸出:2
解釋:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:
輸入:4
輸出:3
解釋:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:
0 <= n <= 30

這道題就是非常經典的動態規劃題目,也是遞歸題目,當然在有些情況下遞歸解題會時間超限,所以學會動態規劃解法還是很重要的;
解題步驟:
1,可以設一個dp[i]的數組,是第i個數,該數組表示第i個數的和;
2,由題目可以輕松找到狀態轉移方程dp[i] = dp[i - 1] + dp[i - 2];
3,通過題目可以看出當i = 0時為dp[0] = 0,而且狀態轉移方程當i小于2時并不滿足,所以單獨列出特殊情況:dp[1] = dp[2] = 1;

代碼如下:

int fib(int N) {vector<int> dp(N + 1, 0);dp[1] = dp[2] = 1;for (int i = 3; i <= N; i++)dp[i] = dp[i - 1] + dp[i - 2];return dp[N]; }

我們還可以將空間復雜度從O(n)優化到O(1),該怎么做呢?我們分析可以發現,每次dp[i]加的值總是它的前兩個,所以我們只需要記錄前兩個值,并不斷更新這兩個值就可以了;

代碼如下:

class Solution { public:int fib(int n) {int a = 0, b = 1, sum;for (int i = 0; i < n; ++i) {sum = a + b;a = b;b = sum;}return a;//最后通過多走一步使sum結果值賦給a,前面就不用寫判斷了} };

來到相似的題練習下:

第 N 個泰波那契數

泰波那契序列 Tn 定義如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的條件下 Tn+3 = Tn + Tn+1 + Tn+2
給你整數 n,請返回第 n 個泰波那契數 Tn 的值。

示例 1:
輸入:n = 4
輸出:4
解釋:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4

示例 2:
輸入:n = 25
輸出:1389537

提示: 0 <= n <= 37
答案保證是一個 32 位整數,即 answer <= 2^31 - 1。

一樣的題意,思路就不寫了直接上代碼:

class Solution { public:int tribonacci(int n) {int dp[n + 1];if (n < 3) return n == 0? 0 : 1;dp[0] = 0;dp[1] = dp[2] = 1;for (int i = 3; i <= n; ++i)dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1];return dp[n];} };

優化版本

class Solution {public:int tribonacci(int n) {int sum, x = 0, y = 1, z = 1;for (int i = 0; i < n; ++i) {sum = x + y + z;x = y;y = z;z = sum;}return x;} }

連續子數組的最大和

輸入一個整型數組,數組中的一個或連續多個整數組成一個子數組。求所有子數組的和的最大值。
要求時間復雜度為O(n)。

示例1:
輸入: nums = [-2,1,-3,4,-1,2,1,-5,4]
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6。

提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100

這是一道求最值的題,非常經典的動態規劃,來分析一下題目
1,dp[i]為nums數組中第i個數的最大值;
2,dp[i] = max(dp[i - 1] + nums[i], nums[i]);當前最大值為前一個最大值加上當前nums的值與當前nums值的最大值,防止dp[i-1]為負數;
3,dp[0] = nums[0];
代碼如下:

class Solution { public:int maxSubArray(vector<int>& nums) {vector<int> dp(nums.size());int Max = dp[0] = nums[0];for (int i = 1; i <nums.size(); ++i) {dp[i] = max(dp[i - 1] + nums[i], nums[i]);Max = max(Max, dp[i]);}return Max;} };

還有其他幾種方法,思想類似,一塊放出來了

方法2

class Solution { public:int maxSubArray(vector<int>& nums) {int max = INT_MIN, sum = 0;for (int i = 0; i < nums.size(); ++i){sum += nums[i];if(max < sum)max = sum;if(sum < 0)sum = 0;}return max;} };

方法3

class Solution { public:int maxSubArray(vector<int>& nums) {int res = nums[0];for (int i = 1; i < nums.size(); ++i) {nums[i] += max(0, nums[i-1]);res = max(nums[i], res);}return res;} };

方法4

class Solution { public:int maxSubArray(vector<int>& nums) {int pre = 0, ans = nums[0];for (int i = 0; i < nums.size(); ++i) {pre = max(pre + nums[i], nums[i]);ans = max(ans, pre);}return ans;} };

哪個好理解看哪個

最小路徑和

給定一個包含非負整數的 m x n 網格 grid ,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。
說明:每次只能向下或者向右移動一步。

示例 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

提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 200
0 <= grid[i][j]<= 100

這道題同樣是求最值問題,思路很簡單,
1,dp[i][j]為到第i行j列的格子的最小和;
2,dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];要不上一行最小,要不上一列最小,最小值加上現在格子的值;
3,dp[0][0] = grid[0][0],且當行為0時(dp[0][i])只能朝右走,列為0時(dp[i][0])只能朝左走;
代碼如下:

class Solution { public:int minPathSum(vector<vector<int>>& grid) {int m = grid.size(), n = grid[0].size();int dp[m][n];dp[0][0] = grid[0][0];for (int i = 1; i < n; ++i) dp[0][i] = dp[0][i - 1] + grid[0][i];for (int i = 1; i < m; ++i)dp[i][0] = dp[i - 1][0] + grid[i][0];for (int i = 1; i < m; ++i) {for (int j = 1; j < n; ++j)dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];}return dp[m - 1][n - 1];} };

零錢兌換

給你一個整數數組 coins ,表示不同面額的硬幣;以及一個整數 amount ,表示總金額。
計算并返回可以湊成總金額所需的 最少的硬幣個數 。如果沒有任何一種硬幣組合能組成總金額,返回 -1 。
你可以認為每種硬幣的數量是無限的。

示例 1:
輸入:coins = [1, 2, 5], amount = 11
輸出:3
解釋:11 = 5 + 5 + 1

示例 2:
輸入:coins = [2], amount = 3
輸出:-1

示例 3:
輸入:coins = [1], amount = 0
輸出:0

示例 4:
輸入:coins = [1], amount = 1
輸出:1

示例 5:
輸入:coins = [1], amount = 2
輸出:2

提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <=104

這道題是騰訊的一道面試題,也是完全背包問題,非常經典,同樣是求最值,思路如下:
1,dp[i]是金額為i的情況下的最少硬幣數;即填滿容量為i的背包最少需要最少硬幣數;
2,dp[i] = min(dp[i - coins[j]] + 1, dp[i]);當前填滿容量i最少需要的硬幣 = min( 之前填滿容量i最少需要的硬幣, 填滿容量 i - coin 需要的硬幣 + 1個當前硬幣)
3,dp[0] = 0;當dp[i]為INT_MAX說明無法組合,為-1;
代碼如下:

class Solution { public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount + 1);dp[0] = 0;int n = coins.size();for (int i = 1; i <= amount; i++) {dp[i] = INT_MAX;for (int j = 0; j < n; j++) {if(i >= coins[j] && dp[i - coins[j]] != INT_MAX) dp[i] = min(dp[i - coins[j]] + 1, dp[i]);}}if (dp[amount] == INT_MAX) dp[amount] = -1;return dp[amount];} };

買賣股票的最佳時機 II

給定一個數組 prices ,其中 prices[i] 是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:
輸入: prices = [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨后,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

示例 2:
輸入: prices = [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之后再將它們賣出。因為這樣屬于同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:
輸入: prices = [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

提示:
1 <= prices.length <= 3 * 104
0 <= prices[i] <= 104

這道題是要求最大利潤,同樣的最值問題,這道題我們卻可以用二維數組來解決,這也是我開始沒有想到的;
1,dp[i][1]代表第i天買入股票的利潤,dp[i][0]代表第i天賣掉股票的利潤,所以這里的二維數組第二維只是用來判斷買入和賣出的;
2,第i天買入: max(若i-1天沒買,則是i天買的,那么就減去花的第i天的錢;若第i-1天已經買了,則不用管)找這兩種情況的最大值,即最大利益,dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
第i天賣出:max(若i-1天沒賣,則是i天賣的,那么就加上收入的第i天的錢;若第i-1天已經賣了,則不用管)找這兩種情況的最大值,即最大利益,dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0]);
3,如果開始不買,則dp[0][0] = 0;
如果開始買入,則dp[0][1] = -prices[0];
代碼如下:

class Solution { public:int maxProfit(vector<int>& prices) {int n = prices.size();int dp[n][2];//第二維1代表買入股票,0代表賣出股票dp[0][0] = 0;dp[0][1] = -prices[0];for (int i = 1; i < n; ++i) {dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0]);dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);}return dp[n - 1][0];} };

代碼當然簡單易懂,但是這個第二維的0,1我是沒有想到;通過這個代碼,我們可以發現每次需要的數據只是前一天買入或者賣出的收入,所以我們可以對代碼進行一個優化,使其空間復雜度從O(n)達到O(1);
只需要設preOut為前一天賣出的(dp[i-1][0]),preGet為前一天買入的(dp[i-1][1])即可
代碼如下:(C++看膩了來個java版)

class Solution {public int maxProfit(int[] prices) {int n = prices.length;int preGet = -prices[0];int preOut = 0;for (int i = 1; i < n; ++i) {int out = Math.max(preGet + prices[i], preOut);int get = Math.max(preOut - prices[i], preGet);preGet = get;preOut = out;}return preOut;} }

所以說,要想真正有優化版本,還是需要列出轉移方程,從而發現規律進行優化;

編輯距離

給你兩個單詞 word1 和 word2,請你計算出將 word1 轉換成 word2 所使用的最少操作數 。
你可以對一個單詞進行如下三種操作:
插入一個字符
刪除一個字符
替換一個字符

示例 1:
輸入:word1 = “horse”, word2 = “ros”
輸出:3
解釋:
horse -> rorse (將 ‘h’ 替換為 ‘r’)
rorse -> rose (刪除 ‘r’)
rose -> ros (刪除 ‘e’)

示例 2:
輸入:word1 = “intention”, word2 = “execution”
輸出:5
解釋:
intention -> inention (刪除 ‘t’)
inention -> enention (將 ‘i’ 替換為 ‘e’)
enention -> exention (將 ‘n’ 替換為 ‘x’)
exention -> exection (將 ‘n’ 替換為 ‘c’)
exection -> execution (插入 ‘u’)

提示:
0 <= word1.length,
word2.length <= 500
word1 和 word2 由小寫英文字母組成

這也是一道求最值問題,但是題目含有字符串,大部分字符串題目都可以用動態規劃,且一般是二維數組,分析如下:
1,dp[i] [j]是,當字符串 word1 的長度為 i,字符串 word2 的長度為 j 時,將 word1 轉化為 word2 所使用的最少操作次數為 dp[i] [j]。
2,dp[i] [j] = dp[i-1] [j-1],此時兩個字符串相同
dp[i] [j] = min(dp[i-1] [j-1],dp[i] [j-1],dp[i-1] [j]) + 1

這個是分了三種情況:
2.1,dp[i-1] [j-1]是替換字符時進行的操作
2.2,dp[i] [j-1]是word1插入時的操作
2.3,dp[i-1] [j]是word1刪除時的操作

3,初始值是計算出所有的 dp[0] [0….n] 和所有的 dp[0….m] [0],因為i和j為0時轉移方程便無法使用;
代碼如下:

class Solution { public:int minDistance(string word1, string word2) {int len1 = word1.length();int len2 = word2.length();vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1));for (int j = 1; j <= len2; ++j) dp[0][j] = dp[0][j - 1] + 1;//word1為空for (int i = 1; i <= len1; ++i) dp[i][0] = dp[i - 1][0] + 1;//word2為空for (int i = 1; i <= len1; ++i) {for (int j = 1; j <= len2; ++j) {if (word1.at(i - 1) == word2.at(j - 1))//word1 ==word2dp[i][j] = dp[i - 1][j - 1];elsedp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;}}return dp[len1][len2];} };

爬樓梯

假設你正在爬樓梯。需要 n 階你才能到達樓頂。
每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?
注意:給定 n 是一個正整數。

示例 1:
輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1 階 + 1 階
2 階

示例 2:
輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1 階 + 1 階 + 1 階
1 階 + 2 階
2 階 + 1 階

這道題就是一道計數類的動態規劃問題,我們通過審題可以發現,這個題就是一個斐波那契數列,那就沒有什么可說的的了,一樣的代碼,再來一遍:

class Solution { public:int climbStairs(int n) {int dp[n+1];dp[0] = 1;dp[1] = 1;for (int i = 2; i <= n; ++i) dp[i] = dp[i - 1] + dp[i - 2];//這里dp[i]是爬到第i階有幾種方法return dp[n];} };

不同路徑

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

示例 1:
輸入:m = 3, n = 7
輸出:28

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

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

示例 4:
輸入:m = 3, n = 3
輸出:6

提示: 1 <= m, n <= 100
題目數據保證答案小于等于 2 * 109

這道題也是一道計數類的題,讓求多少種不同的路徑,分析如下:
1,dp[i][j]是到第i行第j列的格子所有的路徑數;
2,dp[i][j] = dp[i - 1][j] + dp[i][j - 1];我們只能朝下或者朝右走,所以dp[i][j]格子的前一個格子只會是dp[i - 1][j]或者dp[i][j - 1],它倆路徑數之和就是當前格子路徑數;
3,當i為0時或者j為0時,轉移方程不成立,所以我們單獨分析,當i為0時即為第一行,能走到右下角最后一個格子的路徑只有沿邊緣的一條,即為dp[0][j] = 1,同理dp[i][0] = 1;
代碼如下:

class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m, vector<int>(n));int i, j;for (i = 0; i < m; ++i) {for (j = 0; j < n; ++j) {if (!i || !j)dp[i][j] = 1;elsedp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}return dp[i - 1][j - 1];} };

不同路徑 II

一個機器人位于一個 m x n 網格的左上角 (起始點在下圖中標記為“Start” )。
機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記為“Finish”)。
現在考慮網格中有障礙物。那么從左上角到右下角將會有多少條不同的路徑?
網格中的障礙物和空位置分別用 1 和 0 來表示。

示例 1:

輸入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
輸出:2
解釋:
3x3 網格的正中間有一個障礙物。
從左上角到右下角一共有 2 條不同的路徑:
向右 -> 向右 -> 向下 -> 向下
向下 -> 向下 -> 向右 -> 向右

示例 2:

輸入:obstacleGrid = [[0,1],[0,0]]
輸出:1

提示:
m == obstacleGrid.length
n == obstacleGrid[i].length
1 <= m, n <=100
obstacleGrid[i][j] 為 0 或 1

這道題相對上一道題多了障礙物,但是都是一樣的思路,只需要多加上判斷障礙物的條件就可以了,直接上代碼:

class Solution { public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m = obstacleGrid.size(), n = obstacleGrid[0].size();vector<vector<int>> dp(m, vector<int> (n));int i, j;for (i = 0; i < m && !obstacleGrid[i][0]; ++i)dp[i][0] = 1;for (j = 0; j < n && !obstacleGrid[0][j]; ++j)dp[0][j] = 1;for (i = 1; i < m; ++i) {for (j = 1; j < n; ++j) {if(!obstacleGrid[i][j])//若當前位置沒有障礙物dp[i][j] = dp[i-1][j] + dp[i][j-1];}}return dp[m-1][n-1];} };

跳躍游戲

給定一個非負整數數組 nums ,你最初位于數組的 第一個下標 。
數組中的每個元素代表你在該位置可以跳躍的最大長度。
判斷你是否能夠到達最后一個下標。

示例 1:
輸入:nums = [2,3,1,1,4]
輸出:true
解釋:可以先跳 1 步,從下標 0 到達下標 1, 然后再從下標 1 跳 3 步到達最后一個下標。

示例 2:
輸入:nums = [3,2,1,0,4]
輸出:false
解釋:無論怎樣,總會到達下標為 3 的位置。但該下標的最大跳躍長度是 0 , 所以永遠不可能到達最后一個下標。

提示:
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105

這道題其實時一道經典的貪心算法題,但是動態規劃也可以解決,這道題就屬于動態規劃中的判斷類的題目;
1,dp[i]表示是否能達到第i階
2,這個有點特殊就是可以將狀態方程作為一個條件,當可以達到dp[i]的前一階且前一階可以達到dp[i]時,讓dp[i]為true;
3,dp[0] = true,其他情況開始先賦一個初始值false;
代碼如下:

class Solution { public:bool canJump(vector<int>& nums) {int n = nums.size();vector<bool> dp(n);dp[0] = true;//i是階數,j是i的前一階for (int i = 1; i < n; ++i) {dp[i] = false;for (int j = i - 1; j >= 0; --j) {if (dp[j] && j + nums[j] >= i) {dp[i] = true;break;}}}return dp[n-1];} };

這就是幾道動態規劃的小題,當然動態規劃依然有好多不同的類型,只要多做題總結,就能發現其中的套路了

總結

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

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