算法學習筆記(8.2)-動態規劃入門進階

目錄

問題判斷:

問題求解步驟:

圖例:

解析:

方法一:暴力搜索

實現代碼如下所示:

解析:

方法二:記憶化搜索

代碼示例:

解析:

方法三:動態規劃

空間優化

代碼如下

問題判斷:

總的來說,如果一個問題包含重疊子問題、最優子結構,并滿足無后效性,那么它通常適合用動態規劃求解。然而,我們很難從問題的描述中直接提取出這些特征,因此通常需要放寬條件,首先需要觀察問題適不適合使用回溯(窮舉)解決。

適合用回溯解決的問題通常滿足“決策樹模型”,對于問題可以使用樹形結構來描述,其中每一個節點代表一個決策,每一條路徑代表一個決策序列。

換句話說,如果問題包含明確的決策概念,并且解是通過一系列決策產生的,那么它就滿足決策樹模型,通常可以使用回溯解決。

在此基礎上,動態規劃問題還有一些判斷的“加分項”。

1. 問題包含最大(小)或最多(少)等優化描述。

2. 問題的狀態能夠使用一個列表、多維矩陣或樹來表示,并且一個狀態與其周圍的狀態存在遞推關系。

相應地,也存在一些“減分項”。

1. 問題的目標是找出所有的解決方案,而不是找出最優解。

2. 問題描述中有明顯的排列組合特征,需要返回具體的對個方案。

如果一個問題滿足決策樹模型,并具有較為明顯的“加分項”,我們就可以假設它是一個動態規劃問題,并在求解過程中驗證它。

問題求解步驟:

動態規劃的問題解題流程會因為問題的性質和難度有所不同,但通常遵循一下步驟:描述決策,定義狀態,建立dp表,推導狀態轉移方程,確定邊界問題等。

為了理解動態規劃的解題的過程,使用一個經典的例題“最短路徑和”

Question

給定一個n * m的二維網格grid,網格中的每個單元包含一個非負整數,表示該單元格的代價。機器人以左上角單元格為起始點,每次只能向下或者向右移動一步,直至到達右下角單元格。請返回左上角到右下角的最小路徑和。

圖例:

給定網格的最小路徑和為13

第一步:思考每輪的決策,定義狀態,從而得到dp表

本題的每一輪的決策就是從當前格子向下或者向右走一步。設當前格的行列索引為[i,j],則向下或向右走一步后,索引變為[i+1,j]或[i,j+1]。因此,狀態應包含行索引和列索引兩個變量,記為[i,j]。

狀態[i,j]對應的子問題為:從起始點[0,0]走到[i,j]的最小路徑和,記作dp[i,j]。

如圖所示:dp二維矩陣,其尺寸與輸入網格grid相同。

注意點:(Note)

動態規劃和回溯過程可以描述成為一個決策序列,而狀態由所有決策變量構成。應當包含描述解題進度的所有變量,其包含了足夠的信息,能用來推導下一個狀態。

每個狀態都對應一個子問題,我們會定義成為一個dp表來存儲所有子問題的解,狀態的每個獨立變量都是dp表中的一個維度。從本質上看,dp表是狀態和子問題的解之間的映射。

第二步:找出最優子結構,進而推導出狀態轉移方程

對應狀態[i,j],它只能從上邊的格子[i-1,j]和左邊的格子[I,j-1]轉移過來。因此最優子結構為:到達[i,j]的最小路徑和由[i-1,j]的最小路徑和與[I,j-1的最小路徑和哪個較小來決定。

根據以上的分析得出狀態轉移方程為:

dp[i,j] = min(dp[i-1,j],dp[i,j-1]) +grid[i,j]

圖示如下:

注意點:(Note)

根據定義好的dp表,思考原問題和子問題的關系,找出通過子問題的最優解來構造原問題的最優解的方法,即最優子結構。

一旦我們找到了最優子結構,就可以使用它來構建出來狀態轉移方程。

第三步:確定邊界條件和狀態轉移順序

在本題中,處在首行的狀態只能從其左邊的狀態得來,處在首列的狀態只能從其上邊的狀態得來,因此首行i = 0 和首列的 j = 0 是邊界條件。

如圖所示,由于每個格子是由其左方格子和上方格子轉移而來,因此我們使用循環來遍歷矩陣,外循環遍歷各行,內循環遍歷各列。

解析:

根據對上面的理解我們已經可以寫出動態規劃的代碼。然而子問題的解決是一種從頂至底的思想,因此按照“暴力搜索”->“記憶化搜索”->“動態規劃”的順序實現符合思維習慣。

方法一:暴力搜索

從狀態[i,j]開始搜索,不斷分解為更小的狀態[i-1,j]和[i,j-1],遞歸函數包括以下的要素。

  1. 遞歸參數:狀態[i,j]。
  2. 返回值:從[0,0]到[i,j]的最小路徑和dp[i,j]。
  3. 終止條件:當 i = 0 且 j = 0 時,返回代價grid[0,0]。
  4. 剪枝:當i<0時或j<0時索引越界時,此時返回代價+∞,代表不可行。
實現代碼如下所示
# python 代碼示例
def min_path_sum_dfs(grid, i, j) :if i == 0 and j == 0 :return grid[0][0]if i < 0 or j < 0 :return infup = min_path_sum_dfs(grid, i - 1, j)left = min_path_sum_dfs(grid, i, j - 1)return min(up, left) + grid[i][j]
// c++ 代碼示例
int minPathSumDFS(vector<vector<int>> &grid, int i, int j)
{if (i == 0 && j == 0){return grid[0][0] ;}    if (i < 0 or j < 0){retunr INT_MAX ;}int up = minPathSumDFS(grid, i - 1, j) ;int left = minPathSumDFS(grid, i, j - 1) ;return min(up, left) + gird[i][j] ;
}

解析:

給出一個dp[2,1]為根節點的遞歸樹,其中包含一些重疊子問題,其數量會隨著網格grid的尺寸變大而急劇增多。

造成重疊子問題的原因:存在多條路徑可以從左上角到達某一單元格。

每個狀態都有向下和向右兩種選擇,從左上角走到右下角總共需要m+n-2步,所以最差的時間復雜度為O(2^(m+n))。這種計算方法并沒有考慮網格的邊界情況,當到達網格邊界時只剩下一種選擇,因此實際的路徑會少一些。

方法二:記憶化搜索

引入一個與grid網格大小相同的記憶列表mem,用于記錄各個子問題的解,并將重疊子問題進行剪枝。

代碼示例:
// c++ 代碼示例
def min_path_sum_dfs_mem(grid, mem, i, j) :if i == 0 and j == 0 :return grid[0][0]if i < 0 or j < 0 :return infif mem[i][j] != -1 :return mem[i][j]up = min_path_sum_dfs_mem(grid, mem, i - 1, j)left = min_path_sum_dfs_mem(grid, mem, i, j - 1)mem[i][j] = min(up, left) + grid[i][j]return mem[i][j]
// c++ 代碼示例
int minPathSumDFSMem(vector<vector<int>> &grid, vector<vector<int>> &mem, int i, int j) {if (i == 0 && j == 0) {return grid[0][0] ;}if (i < 0 || j < 0) {return INT_MAX ;}if (mem[i][j] != -1) {return mem[i][j] ;}int up = minPathSumDFSMem(grid, mem, i - 1, j) ; int left = minPathSumDFSMem(grid, mem, i, j - 1) ;mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX ;return mem[i][j] ;
}

解析:

在引入記憶化搜索之后,所有的子問題只需要計算一次,因此時間復雜度取決于狀態總數,即網格尺寸O(m*n)

方法三:動態規劃

基于迭代實現動態規劃,代碼如下所示:

# pyhton 代碼示例
def min_path_sum_dp(grid) :n, m = len(grid), len(grid[0])dp = [ [0] * m for _ in range(n)]dp[0][0] = grid[0][0]for j in range(1, m) :dp[0][j] = dp[0][j - 1] + grid[0][j]for i in range(1, n) :dp[i][0] = dp[i - 1][0] + grid[i][0]for i in range(1, n) :for j in range(1, m) :dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + gird[i][j]return dp[n - 1][m - 1]
int minPathSumDP(vector<vector<int>> &grid) {int n = grid.size(), m = grid[0].size();vector<vector<int>> dp(n, vector<int>(m));dp[0][0] = grid[0][0];for (int j = 1; j < m; j++) {dp[0][j] = dp[0][j - 1] + grid[0][j];}for (int i = 1; i < n; i++) {dp[i][0] = dp[i - 1][0] + grid[i][0];}for (int i = 1; i < n; i++) {for (int j = 1; j < m; j++) {dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];}}return dp[n - 1][m - 1];
}

動態規劃的過程如下所示:便利了整個網絡,因此時間復雜度為O(m*n)。

數組的dp的大小也為m * n ,因此空間復雜度為O(m*n)。

空間優化

由于每個格子只與左邊和上邊的格子有關,我們可以只用一個單行數組來實現dp表。

關鍵:數組dp只能表示一行的狀態,我們無法提前初始化首行的狀態,而是在遍歷每行時更新它。

代碼如下:
# python 代碼示例def min_path_sum_dp_comp(grid : list[list[int]]) -> int :n, m = len(grid), len(grid[0])dp = [0] * mdp[0] = grid[0][0]for j in range(1, m) :dp[j] = dp[j - 1] + grid[0][j]for i in range(1, n) :dp[0] = dp[0] + grid[i][0]for j in range(1, m) :dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]return dp[m - 1]
// c++ 代碼示例
int minPathSumDPComp(vector<vector<int>> &grid)
{int n = grid.size(), m = grid[0].size() ;vector<int> dp(m) ;dp[0] = grid[0][0] ;for (int j = 1 ; j < m ; j++){dp[j] = dp[j - 1] + gird[0][j] ;}for (int i = 1 ; i < n ; i++){dp[0] = dp[0] + grid[i][0] ;for (int j = 1 ; j < m ; j++){dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] ;}}return dp[m- 1] ;
}

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

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

相關文章

每日復盤-20240709

今日關注: 20240709 六日漲幅最大: ------1--------300391--------- 長藥控股 五日漲幅最大: ------1--------300391--------- 長藥控股 四日漲幅最大: ------1--------603155--------- 新亞強 三日漲幅最大: ------1--------301300--------- 遠翔新材 二日漲幅最大: ------1-…

基于antdesign封裝一個react的上傳組件

項目中遇到了一個上傳的需求&#xff0c;看了一下已有的代碼很粗糙&#xff0c;而且是直接引用andt的組件&#xff0c;體驗不太好&#xff0c;自己使用FormData對象封裝了一個上傳組件&#xff0c;僅供參考。 代碼如下&#xff1a; /*** FileUploadModal* description - 文件選…

Qt入門(二):Qt的基本組件

目錄 Designer程序面板 1、布局Layout 打破布局 貼合窗口 2、QWidget的屬性 3、Qlabel標簽 顯示圖片 4、QAbstractButton 按鈕類 按鈕組 5、QLineEdit 單行文本輸入框 6、ComboBox 組合框 7、若干與數字相關的組件 Designer程序面板 Qt包含了一個Designer程序 &…

Qt編程技巧總結篇(3)-信號-槽-多線程(二)

文章目錄 Qt編程技巧總結篇&#xff08;3&#xff09;-信號-槽-多線程&#xff08;二&#xff09;主進程與子線程線程同步實例與應用 小結 Qt編程技巧總結篇&#xff08;3&#xff09;-信號-槽-多線程&#xff08;二&#xff09; 多線程學習&#xff0c;使用QMutex&#xff0c;…

RTK_ROS_導航(3):點云的壓縮,PointCloud轉scan

目錄 1. 源碼的安裝2. 修改訂閱的話題3. 可視化1. 源碼的安裝 安裝過程如下 mkdir -p point_to_scan_ws/src cd point_to_scan_ws/src git clone https://github.com/BluewhaleRobot/pointcloud_to_laserscan.git cd .. catkin_make source devel/setup.bash2. 修改訂閱的話題 …

2024.07.01校招 實習 內推 面經

綠*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;內推/實習/校招匯總表格 1、校招 | 元戎啟行2025校園招聘正式批正式啟動&#xff08;內推&#xff09; 校招 | 元戎啟行2025校園招聘正式批正式啟動&#xff08;內推&#xff09; 2、提前批 | 多益網絡2025屆校園…

基于抽象 HandlerInterceptor 快速實現接口鑒權

歡迎關注公眾號&#xff1a;冬瓜白 相關文章&#xff1a; 每天學習一點點之 Spring Web MVC 之抽象 HandlerInterceptor 快速實現常用功能&#xff08;限流、權限等&#xff09; 在[每天學習一點點之 Spring Web MVC 之抽象 HandlerInterceptor 快速實現常用功能&#xff08…

Numpy的廣播機制(用于自動處理不同形狀的數組)

NumPy 廣播是一種強大的機制&#xff0c;允許 NumPy 在執行元素級運算時自動處理不同形狀的數組。廣播的規則使得無需顯式地創建匹配形狀的數組&#xff0c;直接進行運算&#xff0c;大大簡化了代碼并提高了效率。 基本概念 廣播的基本思想是讓較小的數組在需要的維度上進行擴…

【MySQL數據庫之概念性問題】

1、關系型數據庫和非關系型數據庫 關系型數據庫&#xff08;Relational Database&#xff0c;簡稱RDBMS&#xff09;和非關系型數據庫&#xff08;NoSQL Database&#xff09;是兩種不同的數據庫類型。SQL本身叫做結構化查詢語言1、關系型數據庫&#xff1a;&#xff08;MySQL…

Django 更新數據 save()方法

1&#xff0c;添加模型 Test/app11/models.py from django.db import modelsclass Post(models.Model):title models.CharField(max_length200)content models.TextField()pub_date models.DateTimeField(date published)class Book(models.Model):title models.CharFie…

Spring Boot集成grpc快速入門demo

1.什么是GRPC&#xff1f; gRPC 是一個高性能、開源、通用的RPC框架&#xff0c;由Google推出&#xff0c;基于HTTP2協議標準設計開發&#xff0c;默認采用Protocol Buffers數據序列化協議&#xff0c;支持多種開發語言。gRPC提供了一種簡單的方法來精確的定義服務&#xff0c…

UE5.3-基礎藍圖類整理一

常用藍圖類整理&#xff1a; 1、獲取當前關卡名&#xff1a;Get Current LevelName 2、通過關卡名打開關卡&#xff1a;Open Level(by name) 3、碰撞檢測事件&#xff1a;Event ActorBeginOverlap 4、獲取當前player&#xff1a;Get Player Pawn 5、判斷是否相等&#xff1…

深入解析CSS中的!important規則:優先級與最佳實踐

先上實踐&#xff0c;再討論設計 在實際工程中&#xff0c;!important 的使用場景通常出現在需要確保某個樣式規則具有最高優先級&#xff0c;以覆蓋其他可能沖突的樣式規則時。以下是一個具體的例子&#xff1a; 場景描述 假設你正在開發一個網站&#xff0c;該網站使用了多…

JavaScript的數組與函數

數組 <script type"text/javascript">/** 知識點&#xff1a;數組* 理解&#xff1a;一維數組的容器* 概念&#xff1a;* 1.數組中的數據叫做元素* 2.元素都有編號叫做下標/索引* 3.下標從0開始* 注意&#xff1a;* 1.數組作為數據的容器…

【JavaScript腳本宇宙】狀態管理利器:JavaScript 庫全面解析

提升項目效率與可維護性&#xff1a;JavaScript 狀態管理庫大揭秘 前言 在現代前端開發中&#xff0c;狀態管理是一個至關重要的話題。隨著復雜性的增加&#xff0c;有效地管理應用程序的狀態變得越來越具有挑戰性。本文將介紹一些流行的 JavaScript 庫&#xff0c;這些庫提供…

WEB安全基礎:網絡安全常用術語

一、攻擊類別 漏洞&#xff1a;硬件、軟件、協議&#xff0c;代碼層次的缺陷。 后?&#xff1a;方便后續進行系統留下的隱蔽后?程序。 病毒&#xff1a;一種可以自我復制并傳播&#xff0c;感染計算機和網絡系統的惡意軟件(Malware)&#xff0c;它能損害數據、系統功能或攔…

C++語言學習精簡筆記(包含C++20特性)

目錄 1 C新語法C與CC編譯運行String編程范式C基礎類型**自動類型推導**統一對象初始化&#xff1a;Uniform Initialization 控制結構if語句for語句switch語句namespace 2 函數函數聲明形式參數函數參數傳遞的選擇函數返回值的選擇 函數重載 Lambda表達式函數的定義和申明生存期…

磁力貓磁力搜索大全教程,如何使用磁力鏈接

磁力鏈接是一種特殊的下載鏈接&#xff0c;磁力鏈接可以理解為一個文件識別碼&#xff0c;而并非具體的資源地址&#xff0c;下載軟件需要拿著這個識別碼去整個互聯網(DHT網絡)去尋找持有該資源的用戶(節點)&#xff0c;如果找到則可以進行傳輸下載。一般年代越久遠的磁力鏈接下…

【一】m2芯片的mac中安裝ubuntu24虛擬機集群

文章目錄 1. 虛擬機配置2. 復制虛擬機2.1 修改主機名2.2 修改網絡 1. 虛擬機配置 在官方網站下載好ubuntu24-arm版鏡像開始安裝&#xff0c;安裝使用VMWare Fusion的社區免費授權版,使用一臺m2芯片的mac電腦作為物理機平臺。 為什么選擇ubuntu24&#xff1f;因為centOS7目前已…

Proteus + Keil單片機仿真教程(五)多位LED數碼管的靜態顯示

Proteus + Keil單片機仿真教程(五)多位LED數碼管 上一章節講解了單個數碼管的靜態和動態顯示,這一章節將對多個數碼管的靜態顯示進行學習,本章節主要難點: 1.鎖存器的理解和使用; 2.多個數碼管的接線封裝方式; 3.Proteus 快速接頭的使用。 第一個多位數碼管示例 元件…