【C++】動態規劃從入門到精通

一、動態規劃基礎概念詳解

什么是動態規劃
動態規劃(Dynamic Programming,DP)是一種通過將復雜問題分解為重疊子問題,并存儲子問題解以避免重復計算的優化算法。它適用于具有以下兩個關鍵性質的問題:

最優子結構:問題的最優解包含子問題的最優解

重疊子問題:不同決策序列會重復求解相同的子問題

下面用一些例子(由淺入深)了解動態規劃

1.1 斐波那契數列遞歸實現解析

int fib(int n) {if(n <= 1) return n;          // 基準條件:F(0)=0, F(1)=1return fib(n-1) + fib(n-2);  // 遞歸分解為兩個子問題
}

代碼解析

  1. 遞歸終止條件:當n<=1時直接返回n值
  2. 遞歸關系:F(n) = F(n-1) + F(n-2)
  3. 問題分析:計算F(5)需要計算F(4)和F(3),而計算F(4)又需要F(3)和F(2),存在大量重復計算
  4. 時間復雜度:二叉樹結構,O(2^n),空間復雜度O(n)(調用棧深度)

1.2 記憶化遞歸實現解析

int memo[100] = {0};  // 全局記憶數組,默認初始化為0int fib_memo(int n) {if(n <= 1) return n;if(memo[n] != 0)  // 檢查是否已計算過return memo[n];return memo[n] = fib_memo(n-1) + fib_memo(n-2);  // 計算結果并存儲
}

代碼解析

  1. memo數組存儲已計算結果,初始值為0表示未計算
  2. 每次遞歸調用前檢查是否已有緩存結果
  3. 通過空間換時間,將重復計算轉化為查表操作
  4. 時間復雜度優化到O(n),空間復雜度O(n)

1.3 迭代法實現解析

int fib_tab(int n) {if(n == 0) return 0;int dp[n+1];          // 創建DP表dp[0] = 0;            // 初始化基礎條件dp[1] = 1;for(int i=2; i<=n; ++i)dp[i] = dp[i-1] + dp[i-2];  // 遞推填充表格return dp[n];
}

代碼解析

  1. dp數組索引對應斐波那契數列的位置
  2. 初始化前兩個已知值
  3. 循環從2開始逐步構建后續結果
  4. 時間復雜度O(n),空間復雜度O(n)(可優化為O(1))

二、經典問題深度解析

2.1 最長公共子序列(LCS)完整解析

問題描述:給定兩個字符串text1和text2,返回它們的最長公共子序列的長度

int lcs(string text1, string text2) {int m = text1.size(), n = text2.size();// 創建(m+1)x(n+1)的二維DP表,+1是為了處理空字符串的情況vector<vector<int>> dp(m+1, vector<int>(n+1, 0));for(int i=1; i<=m; ++i) {for(int j=1; j<=n; ++j) {if(text1[i-1] == text2[j-1])  // 字符匹配(注意索引偏移)dp[i][j] = dp[i-1][j-1] + 1;else  // 不匹配時取兩個可能方向的最大值dp[i][j] = max(dp[i-1][j], dp[i][j-1]);}}return dp[m][n];
}

代碼解析

  1. 狀態定義:dp[i][j]表示text1前i個字符與text2前j個字符的LCS長度
  2. 初始化:第一行和第一列初始為0,表示空字符串的情況
  3. 狀態轉移:
    • 當字符匹配時:LCS長度+1,繼承左上方值+1
    • 當字符不匹配時:取上方或左方的最大值
  4. 遍歷順序:雙重循環按行填充表格
  5. 示例分析:
    text1 = “abcde”, text2 = “ace”
    DP表最終值為3(LCS為"ace")

2.2 0-1背包問題完整解析

問題描述:給定物品重量數組wt和價值數組val,背包容量W,求能裝的最大價值

int knapsack(int W, vector<int>& wt, vector<int>& val) {int n = wt.size();vector<int> dp(W+1, 0);  // 一維DP數組優化空間for(int i=0; i<n; ++i) {            // 遍歷每個物品for(int w=W; w>=wt[i]; --w) {   // 逆序更新防止覆蓋dp[w] = max(dp[w],                    // 不選當前物品dp[w - wt[i]] + val[i]);  // 選擇當前物品}}return dp[W];
}

代碼解析

  1. 狀態定義:dp[w]表示容量為w時的最大價值
  2. 空間優化:使用一維數組替代二維數組
  3. 逆序遍歷原因:保證每個物品只被考慮一次,避免重復使用
  4. 狀態轉移方程分析:
    • 不選物品i:價值保持dp[w]不變
    • 選物品i:價值為dp[w-wt[i]] + val[i]
  5. 示例分析:
    W=4, wt=[2,3,4], val=[3,4,5]
    最終dp[4] = max(不選4: dp[4], 選4: dp[0]+5) = 5

2.3 編輯距離完整解析

問題描述:計算將word1轉換成word2所需的最小操作次數(插入、刪除、替換)

int minDistance(string word1, string word2) {int m = word1.size(), n = word2.size();vector<vector<int>> dp(m+1, vector<int>(n+1, 0));// 初始化邊界條件for(int i=0; i<=m; ++i) dp[i][0] = i;  // 刪除i次for(int j=0; j<=n; ++j) dp[0][j] = j;  // 插入j次for(int i=1; i<=m; ++i) {for(int j=1; j<=n; ++j) {if(word1[i-1] == word2[j-1]) {  // 字符相同無需操作dp[i][j] = dp[i-1][j-1];} else {  // 選擇三種操作中的最小代價dp[i][j] = 1 + min({dp[i-1][j],   // 刪除word1字符dp[i][j-1],   // 插入word2字符dp[i-1][j-1]});// 替換字符}}}return dp[m][n];
}

代碼解析

  1. 狀態定義:dp[i][j]表示轉換前i個字符到前j個字符的最小操作數
  2. 邊界初始化:
    • 第一列表示將word1刪成空串的操作次數
    • 第一行表示將空串插入成word2的操作次數
  3. 狀態轉移分析:
    • 字符匹配:直接繼承左上方值
    • 字符不匹配:取三種操作的最小值+1
  4. 操作類型對應關系:
    • 刪除:相當于處理word1的前i-1個字符
    • 插入:相當于處理word2的前j-1個字符
    • 替換:相當于處理i-1和j-1的情況后修改字符
  5. 示例分析:
    word1 = “horse”, word2 = “ros”
    最終編輯距離為3(替換h→r,刪除 r,刪除 e)

三、動態規劃優化技巧詳解

3.1 斐波那契數列空間優化

int fib_opt(int n) {if(n == 0) return 0;int prev = 0, curr = 1;  // 初始值F(0)=0, F(1)=1for(int i=2; i<=n; ++i) {int next = prev + curr;  // 計算下一個值prev = curr;  // 更新前一個值curr = next;  // 更新當前值}return curr;
}

優化原理

  1. 觀察發現每個狀態只依賴前兩個狀態
  2. 使用兩個變量代替數組存儲歷史值
  3. 空間復雜度從O(n)降到O(1)
  4. 滾動更新機制:
    • 每次迭代將prev更新為前一個curr
    • curr更新為新的計算結果

3.2 背包問題空間優化

// 二維原始版本
int dp[n+1][W+1]; // 優化為一維數組
vector<int> dp(W+1, 0);

優化原理

  1. 二維數組中每一行只依賴上一行的數據
  2. 逆序更新避免覆蓋未使用的舊值
  3. 關鍵點:內層循環必須從W到wt[i]逆序進行
  4. 示例說明:
    • 正序更新會導致物品被重復選取(完全背包問題)
    • 逆序更新保證每個物品只被考慮一次

四、動態規劃解題方法論

4.1 狀態定義技巧

  1. 確定問題變量維度:

    • 單序列問題(如LIS):一維狀態dp[i]
    • 雙序列問題(如LCS):二維狀態dp[i][j]
    • 帶約束問題(如背包):二維狀態dp[i][w]
  2. 常見狀態定義模式:

    • “前i個元素…”:如dp[i]表示前i個元素的最優解
    • “以第i個元素結尾…”:如最長遞增子序列問題
    • “位置(i,j)…”:如矩陣路徑問題

4.2 狀態轉移方程建立

  1. 分析子問題關系:

    • 如何從較小規模的子問題推導當前問題
    • 舉例:在編輯距離中,三種操作對應三種子問題轉移路徑
  2. 方程建立步驟:
    (1) 列出所有可能的決策選項
    (2) 計算每個決策對應的子問題解
    (3) 選擇最優決策并組合結果

4.3 初始化技巧

  1. 邊界條件處理:

    • 空字符串/空集合的處理
    • 初始值的物理意義(如背包容量為0時價值為0)
  2. 特殊值初始化示例:

    // 矩陣路徑問題初始化第一行和第一列
    for(int i=0; i<m; ++i) dp[i][0] = 1;
    for(int j=0; j<n; ++j) dp[0][j] = 1;
    

五、綜合案例分析

5.1 最大子數組和

問題描述:求整數數組中和最大的連續子數組

int maxSubArray(vector<int>& nums) {int currMax = nums[0], globalMax = nums[0];for(int i=1; i<nums.size(); ++i) {// 決策:繼續擴展子數組 or 重新開始currMax = max(nums[i], currMax + nums[i]);// 更新全局最大值globalMax = max(globalMax, currMax);}return globalMax;
}

算法解析

  1. 狀態定義:currMax表示以當前元素結尾的最大子數組和
  2. 狀態轉移方程:
    currMax = max(nums[i], currMax + nums[i])
  3. 空間優化:僅需維護兩個變量
  4. 示例分析:
    輸入:[-2,1,-3,4,-1,2,1,-5,4]
    輸出:6(子數組[4,-1,2,1])

5.2 不同路徑問題

問題描述:m x n網格從左上角到右下角的唯一路徑數

int uniquePaths(int m, int n) {vector<vector<int>> dp(m, vector<int>(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];
}

算法解析

  1. 狀態定義:dp[i][j]表示到達(i,j)的路徑數
  2. 狀態轉移方程:dp[i][j] = 上方路徑數 + 左方路徑數
  3. 初始化技巧:第一行和第一列都只有1種路徑
  4. 空間優化:可用一維數組替代,dp[j] += dp[j-1]

六、動態規劃調試技巧

6.1 DP表可視化

  1. 打印DP表中間狀態
    // 在LCS代碼中插入調試輸出
    for(auto& row : dp) {for(int val : row) cout << val << " ";cout << endl;
    }
    
  2. 觀察表數據是否符合預期

6.2 邊界測試用例

  1. 空輸入測試:空字符串、空數組等
  2. 極值測試:n=0, W=0等特殊情況
  3. 示例測試:使用題目給出的示例驗證

6.3 常見錯誤排查

  1. 數組越界:檢查索引是否正確(特別是從1開始的情況)
  2. 初始化錯誤:驗證邊界條件是否正確設置
  3. 循環順序錯誤:檢查是否按正確依賴順序填充表格
  4. 狀態轉移方程錯誤:用簡單用例手動驗證

七、動態規劃復雜度分析指南

7.1 時間復雜度計算

  1. 基本公式:狀態數 × 每個狀態的轉移成本

    • LCS問題:O(mn)狀態 × O(1)轉移 = O(mn)
    • 背包問題:O(nW)狀態 × O(1)轉移 = O(nW)
  2. 多項式時間與偽多項式時間:

    • 背包問題的O(nW)稱為偽多項式時間
    • 當W很大時(如指數級),算法效率會顯著下降

7.2 空間復雜度優化

  1. 滾動數組技巧:

    • 二維→一維:當當前行只依賴前一行時
    • 示例:斐波那契數列、背包問題
  2. 狀態壓縮技巧:

    • 使用位運算表示狀態集合
    • 常見于旅行商問題(TSP)等狀壓DP

八、動態規劃進階路線圖

8.1 學習路徑建議

  1. 基礎階段(1-2周):

    • 斐波那契數列
    • 爬樓梯問題
    • 最大子數組和
  2. 提高階段(2-4周):

    • 背包問題系列
    • 字符串編輯問題
    • 矩陣路徑問題
  3. 精通階段(1-2月):

    • 樹形DP(二叉樹最大路徑和)
    • 狀態壓縮DP(TSP問題)
    • 區間DP(矩陣鏈乘法)

8.2 推薦練習題目

題目類型LeetCode題號難度
爬樓梯70簡單
最長遞增子序列300中等
零錢兌換322中等
正則表達式匹配10困難
買賣股票最佳時機121/123中等

九、動態規劃代碼模板庫

9.1 一維DP模板

int dp[n]; 
dp[0] = initial_value;for(int i=1; i<n; ++i) {dp[i] = compute(dp[...]);
}return dp[n-1];

9.2 二維DP模板

vector<vector<int>> dp(m, vector<int>(n, 0));// 初始化邊界
for(int i=0; i<m; ++i) dp[i][0] = ...;
for(int j=0; j<n; ++j) dp[0][j] = ...;// 填充表格
for(int i=1; i<m; ++i) {for(int j=1; j<n; ++j) {dp[i][j] = compute(dp[i-1][j], dp[i][j-1], ...);}
}

十、動態規劃常見問題FAQ

Q:如何判斷一個問題是否可以用DP解決?
A:檢查問題是否具有:

  1. 最優子結構性質
  2. 重疊子問題性質
  3. 無后效性(當前決策不影響之前狀態)

Q:DP和分治法的區別是什么?
A:分治法將問題分解為獨立的子問題,而DP處理的是重疊的子問題

Q:如何處理環形結構問題?
A:常用技巧:

  1. 破環成鏈(復制數組)
  2. 分類討論(考慮包含首元素和不包含的情況)

Q:如何選擇記憶化遞歸還是迭代法?
A:

  • 記憶化遞歸更直觀,適合樹形結構問題
  • 迭代法效率更高,適合需要空間優化的情況
  • 動態規劃導圖
    在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/73905.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/73905.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/73905.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Qt動態設置樣式,實現樣式實時切換

文章目錄 概要插件實現界面 核心代碼設置樣式 擴展導入樣式導出樣式 概要 最近需要設計界面&#xff0c;但是使用Qt的Designer只能看到每個界面單獨的樣式&#xff0c;程序中有些事需要主界面調用進行組合的界面&#xff0c;因此需要寫一個插件Ui可以直接輸入樣式內容&#xf…

集成學習之隨機森林

目錄 一、集成學習的含義 二、集成學習的代表 三、集成學習的應用 1、分類問題集成。&#xff08;基學習器是分類模型&#xff09; 2、回歸問題集成。&#xff08;基學習器是回歸模型&#xff09; 3、特征選取集成。 四、Bagging之隨機森林 1、隨機森林是有多個決策樹&a…

矩陣期望 E 的含義:概率

矩陣期望 E 的含義:概率 期望的含義 在概率論和統計學中,數學期望(或均值,簡稱期望)是試驗中每次可能結果的概率乘以其結果的總和,是最基本的數學特征之一,它反映隨機變量平均取值的大小。用公式表示,如果離散型隨機變量 X X X 可能取值為 x i x_

Qt Graphics View

Graphics View框架是用來處理大量2D圖形對象的&#xff0c;適合需要高效管理和交互的場景&#xff0c;比如繪圖軟件、地圖編輯或者游戲。它和QPainter的區別在于&#xff0c;Graphics View提供了更高級別的對象管理&#xff0c;而QPainter更偏向于直接繪制。 一、核心組件 ?Q…

卷積神經網絡 - 卷積層(具體例子)

為了更一步學習卷積神經網絡之卷積層&#xff0c;本文我們來通過幾個個例子來加深理解。 一、灰度圖像和彩色圖像的關于特征映射的例子 下面我們通過2個例子來形象說明卷積層中“特征映射”的概念&#xff0c;一個針對灰度圖像&#xff0c;一個針對彩色圖像。 例子 1&#x…

xlsx.utils.json_to_sheet函數詳解

xlsx.utils.json_to_sheet 是 xlsx 庫中的一個實用函數&#xff0c;用于將 JSON 數據轉換為 Excel 工作表對象。這個函數非常有用&#xff0c;尤其是在你需要從數據庫或其他數據源獲取數據并將其導出到 Excel 文件時。 函數簽名 XLSX.utils.json_to_sheet(data, opts)data&am…

2025-03-17 學習記錄--C/C++-PTA 習題4-7 最大公約數和最小公倍數

合抱之木&#xff0c;生于毫末&#xff1b;九層之臺&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、題目描述 ?? 習題4-7 最大公約數和最小公倍數 本題要求兩個給定正整數的最大公約數和最小公倍數。 輸入格式: 輸入在一…

【源碼閱讀】多個函數抽象為類(實現各種類型文件轉為PDF)

目錄 一、原始函數二、類三、轉換過程 一、原始函數 最開始就是寫了幾個函數&#xff08;包括doc、excel、ppt類型的文件&#xff09;轉換為pdf&#xff0c;需要將這些函數形成一個類。相似的一類函數就可以組成一個實現特定功能的類 import subprocess import pandas as pd i…

VSCode擴展工具Copilot MCP使用教程【MCP】

MCP&#xff08;Model Context Protocol&#xff0c;模型上下文協議&#xff09; &#xff0c;2024年11月底&#xff0c;由 Anthropic 推出的一種開放標準&#xff0c;旨在統一大型語言模型&#xff08;LLM&#xff09;與外部數據源和工具之間的通信協議。本文章教你使用VSCode…

【leetcode100】搜索插入位置

1、題目描述 給定一個排序數組和一個目標值&#xff0c;在數組中找到目標值&#xff0c;并返回其索引。如果目標值不存在于數組中&#xff0c;返回它將會被按順序插入的位置。 請必須使用時間復雜度為 O(log n) 的算法。 示例 1: 輸入: nums [1,3,5,6], target 5 輸出: 2…

【小白向】Word|Word怎么給公式標號、調整公式字體和花括號對齊

【小白向】Word&#xff5c;Word怎么給公式標號、調整公式字體和花括號對齊 我的版本&#xff1a;Word 2021 如需快速查看關鍵步驟&#xff0c;請直接閱讀標紅部分。 如果遇到無法調整的情況&#xff0c;可以直接下載我的示例文檔進行參考&#xff1a;花括號和其他的示例公式.…

【算法day15】最接近的三數之和

最接近的三數之和 給你一個長度為 n 的整數數組 nums 和 一個目標值 target。請你從 nums 中選出三個整數&#xff0c;使它們的和與 target 最接近。 這里是引用 返回這三個數的和。 假定每組輸入只存在恰好一個解。 https://leetcode.cn/problems/3sum-closest/submissions/61…

Blender-MCP服務源碼5-BlenderSocket插件安裝

Blender-MCP服務源碼5-BlenderSocket插件安裝 上一篇講述了Blender是基于Socket進行本地和遠程進行通訊&#xff0c;現在嘗試將BlenderSocket插件安裝到Blender中進行功能調試 1-核心知識點 將開發的BlenderSocket插件安裝到Blender中 2-思路整理 1&#xff09;將SocketServe…

【MySQL數據庫】存儲過程與自定義函數(含: SQL變量、分支語句、循環語句 和 游標、異常處理 等內容)

存儲過程&#xff1a;一組預編譯的SQL語句和流程控制語句&#xff0c;被命名并存儲在數據庫中。存儲過程可以用來封裝復雜的數據庫操作邏輯&#xff0c;并在需要時進行調用。 類似的操作還有&#xff1a;自定義函數、.sql文件導入。 我們先從熟悉的函數開始說起&#xff1a; …

ASP3605抗輻照加固同步降壓調節器——商業航天電源芯片解決方案新選擇

ASP3605企業宇航級型號ASP3605S2U通過SEU≥75 MeVcm/mg與SEL≥75 MeVcm/mg抗輻射測試。其輸入電壓4V至15V&#xff0c;輸出電流5A&#xff0c;支持多相級聯與冗余設計&#xff0c;適用于衛星、航天器電源系統。 面向航天場景的核心功能設計 1. 抗輻射與可靠性保障 單粒子效應…

使用fastapi部署stable diffusion模型

使用vscode運行stable diffusion模型&#xff0c;每次加載模型都需要10分鐘&#xff0c;為算法及prompt調試帶來了極大麻煩。使用jupyter解決自然是一個比較好的方案&#xff0c;但如果jupyter由于種種原因不能使用時&#xff0c;fastapi無疑成為了一個很好的選擇。 參考github…

2025-03-16 學習記錄--C/C++-PTA 習題4-4 特殊a串數列求和

合抱之木&#xff0c;生于毫末&#xff1b;九層之臺&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、題目描述 ?? 習題4-4 特殊a串數列求和 給定兩個均不超過9的正整數a和n&#xff0c;要求編寫程序求aaaaaa?aa?a&#x…

ffmpeg庫視頻硬編碼使用流程

?一、硬件編碼核心流程? ?硬件設備初始化 // 創建CUDA硬件設備上下文? AVBufferRef *hw_device_ctx NULL; av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0);// 綁定硬件設備到編碼器上下文? codec_ctx->hw_device_ctx av_buffer_…

【設計模式】3W 學習法全面解析 7 大結構型模式:Java 實戰 + 開源框架應用

3W 學習法總結結構型模式&#xff08;附 Java 代碼實戰及開源框架應用&#xff09; 結構型模式 主要關注 類與對象的組合&#xff0c;確保不同組件之間能夠高效協作&#xff0c;提高系統的靈活性和可維護性。本文采用 3W 學習法&#xff08;What、Why、How&#xff09;&#x…

在大數據開發中ETL是指什么?

hello寶子們...我們是艾斯視覺擅長ui設計和前端數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩! 在數字經濟時代&#xff0c;數據已成為企業最核心的資產。然而&#xff0c;分散在業務系統、日志文件…