[Lc6_記憶化搜索] 掃雷游戲 | 理解 遞歸vs記憶化搜索vs dp

目錄

?1.掃雷游戲

題解

1.記憶化搜索

解法一:遞歸

解法二:記憶化搜索

解法三:動態規劃


?1.掃雷游戲 (暴力+模擬)

鏈接:529. 掃雷游戲

讓我們一起來玩掃雷游戲!

給你一個大小為 m x n 二維字符矩陣 board ,表示掃雷游戲的盤面,其中:

  • 'M' 代表一個 未挖出的 地雷,
  • 'E' 代表一個 未挖出的 空方塊,
  • 'B' 代表沒有相鄰(上,下,左,右,和所有4個對角線)地雷的 已挖出的 空白方塊,
  • 數字'1''8')表示有多少地雷與這塊 已挖出的 方塊相鄰,
  • 'X' 則表示一個 已挖出的 地雷。

給你一個整數數組 click ,其中 click = [clickr, clickc] 表示在所有 未挖出的 方塊('M' 或者 'E')中的下一個點擊位置(clickr 是行下標,clickc 是列下標)。

根據以下規則,返回相應位置被點擊后對應的盤面:

  1. 如果一個地雷('M')被挖出,游戲就結束了- 把它改為 'X'
  2. 如果一個 沒有相鄰地雷 的空方塊('E')被挖出,修改它為('B'),并且所有和其相鄰的 未挖出 方塊都應該被遞歸地揭露。
  3. 如果一個 至少與一個地雷相鄰 的空方塊('E')被挖出,修改它為數字('1''8' ),表示相鄰地雷的數量。
  4. 如果在此次點擊中,若無更多方塊可被揭露,則返回盤面。


題解

這題說的很多,其實就是給一個mxn的棋盤

再給一個棋盤坐標,點擊這個坐標,把修改后的棋盤返回。

我們要注意一下規則:

  • ‘M’ 代表一個 未挖出的 地雷,
  • E’ 代表一個 未挖出的 空方塊,
  • B’ 代表沒有相鄰(上,下,左,右,和所有4個對角線)地雷的 已挖出的 空白方塊,

前面我們都只研究上下左右,這里還要考慮斜對角線4個位置。

  • 也就是說如果當前被挖的空格周圍沒有地雷,就把它標記成B。
  • 數字(‘1’ 到 ‘8’)表示有多少地雷與這塊 已挖出的 方塊相鄰,
  • 數字 表示這個被挖的格子周圍有多少個地雷
  • ‘X ’ 則表示一個 已挖出的 地雷。

根據以下規則,返回相應位置被點擊后對應的盤面:

  • 如果一個地雷(‘M’)被挖出,游戲就結束了- 把它改為 ‘X’ 。
  • 也就是說如果剛開始給你的這個位置就是雷的話,把這個位置改成‘X’,直接結束即可!

  • 處理 E 的話,存在 兩種情況:
  • 如果一個 沒有相鄰地雷 的空方塊(‘E’)被挖出,修改它為(‘B’)
    • 并且所有和其相鄰的 未挖出 方塊‘E’ 都應該被遞歸地 揭露。
    • 如果當前被挖的位置周圍沒有地雷,把它改成’B‘,然后 遞歸 的往周圍走。

  • 如果一個 至少與一個地雷相鄰 的空方塊(‘E’)被挖出
    • 修改它為數字(‘1’ 到 ‘8’ ),表示相鄰地雷的數量。
    • 如果當前被挖的位置周圍有地雷,把它修改成周圍的地雷數,然后就 不要遞歸下去。直接返回。

如果在此次點擊中,若無更多方塊可被揭露,則返回盤面。

原理:

其實這就是一個模擬,已經告訴怎么去操作了。

當點擊這個位置之后,我們要先統計一下點擊的這個位置周圍有沒有地雷。

  • 周圍沒有地雷,就把這個位置改成’B‘,然后遞歸的把周圍所有位置都找一遍。
  • 如果周圍有地雷的話,就把這個位置改成地雷數,然后就不要從這個位置在遞歸下去了,返回即可。
  • 同理遞歸進去也是如上面一樣先統計周圍有沒有地雷。。。。

不過這里有個細節問題,我們之前是沿著一個位置上下左右找4個位置。但是這個位置要找一圈8個位置。

  • 我們還是和之前一樣,我們直接把向量數組擴展一下就可以了。
  • 可以寫兩個-1,1之后然后給它們分別匹配-1,1。

八鄰域搜索

初始時只有M和E

  • 我們查找到E的時候進行遞歸
  • 標記為B和數字的時候,已經幫我們實現cheak功能啦?

E要么變為B(遞歸),要么變為數字(統計,不遞歸)

class Solution {
public:int dx[8] = {0,0,1,-1,-1,-1,1,1};int dy[8] = {1,-1,0,0,1,-1,1,-1};int m, n;vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {m = board.size();n = board[0].size();int i = click[0], j = click[1];if (board[i][j] == 'M') {board[i][j] = 'X';return board;}dfs(board, i, j);return board;}void dfs(vector<vector<char>>& board, int i, int j) {int count = 0;//先計算周圍地雷數量for (int k = 0; k < 8; ++k) {int x = i + dx[k], y = j + dy[k];if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'M') {++count;}}if (count > 0) {board[i][j] = count + '0'; // 顯示地雷數后停止遞歸} else {board[i][j] = 'B'; for (int k = 0; k < 8; ++k) {int x = i + dx[k], y = j + dy[k];if (x >= 0 && x < m && y >= 0 && y < n && board[x][y]=='E') {dfs(board, x, y); //數字停止遞歸//B標記以掃描,停止遞歸//不斷對未掃描的E進行掃描}}}}
};

1.記憶化搜索

什么是記憶化搜索,下面我們以一道比較常見的 509. 斐波那契數 來演示一下什么是記憶化搜索。

  • 關于這個斐波那契數,我們很早之前就碰到過如循環、遞推、遞歸
  • 不過它 還可以用動態規劃,記憶化搜索來解決。矩陣快速冪也可以解決。

時間復雜度:

  • 循環、遞推、遞歸,時間復雜度O(N^2)
  • 動態規劃、記憶化搜索,時間復雜度O(N)
  • 矩陣快速冪,時間復雜度O(logN)

因為記憶化搜索是基于遞歸代碼來實現的,所以我們先用遞歸寫這道題。

解法一:遞歸

dfs要干的事情就是給我一個數我把它第n個斐波那契數返回來。

關于函數體怎么寫,很簡單求。

  • F(0) = 0,F(1) = 1,F(n) = F(n - 1) + F(n - 2),其中 n > 1。
  • 求第n個斐波那契數先把n-1和n-2斐波那契數求出來。
  • 遞歸出口 n<=1 返回n就可以了。
int fib(int n)
{return dfs(n);
}int dfs(int n){if(n <= 1) return n;    return dfs(n-1)+dfs(n-2);}

我們先分析一下這個遞歸過程。

  • 當n=5的時候,我們分析一下這個遞歸展開過程。
  • 就像一顆二叉樹。
  • 有多少節點就要遞歸多少次。時間復雜度O(2^N)

我們先分析一下為什么這個遞歸它會這么慢。

慢其實就慢在我們會重復計算一些問題,如d(3)我們會重復進入兩次,但是這兩個d(3)的遞歸展開樹是完全一樣的!

這兩個d(3)向上返回的值是完全一樣的!

那我們想一下能不能這樣優化一下,來一個備忘錄

  • 就是當我從d(5)來深度優先遍歷的時候,先去左邊
  • 然后當我從d(3)這顆遞歸樹返回時是一個返回值,(x為返回值)
  • 此時把d(3)=x這個f信息丟到備忘錄里。以此來避免之后的重復計算

(備忘錄可能是一個數組、哈希表)。

  • 然后當從左邊回來然后去右邊的時候,當我們又一次進入d(3)的時候,此時我就不把d(3)展開了。
  • 因為此時我去備忘錄里找找我發現d(3)=x這個消息在左邊就已經計算過了。
  • 所以在這里不展開了,直接在備忘錄里把x給拿出來(爽了家人們),然后返回就可以了。

當有一個備忘錄的時候,相同子樹不在展開的時候,是不是就對遞歸做優化了。

像這樣一種方式就叫做記憶化搜索!

解法二:記憶化搜索

在遞歸過程中,發現有一些 完全相同的問題

  • 我們就可以把完全相同問題的結果放到備忘錄里
  • 然后遞歸進入相同問題的時候直接往備忘錄里拿結果就可以了。
  • 這就是記憶化搜索,因此又稱作帶備忘錄的遞歸。

此時你會發現其實并不止d(3)會被做優化其實就是剪枝,d(2)也會被優化。

這么多分支都不用進去。

  • 時間復雜度直接從O(2^N)變成O(N)。
  • 所以添加一個備忘錄可以極大優化我們的搜索過程。
  • 這也是記憶化搜索名字的由來,帶著記憶去做dfs,這些重復的地方就不要重復進去了就實現了大量的剪枝

時間復雜度從指數級別降到線性級別

如何實現記憶化搜索呢?

  • 添加一個備忘錄 memo
  • 遞歸每次返回的時候,將結果放到備忘錄里面
  • 在每次進入遞歸的時候,往備忘錄里面瞅一瞅

開始前瞅一瞅,返回前存一存~

那添加一個什么樣的備忘錄呢?

緊盯這樣一個原則,先找可變參數,然后將<可變參數,返回值>的映射關系存起來。

  • 在這個dfs遞歸函數里。可變參數就是n,返回值就是第幾個斐波那契數。
  • 所以在這里僅需<int,int> 前面是第幾個斐波那契數的第幾,后面是存的是具體的斐波那契數。

這個備忘錄搞什么數據結構呢?

  • 可以搞一個哈希表,這里我們可以來一個數組就行
  • 這個數組可以搞成全局的,也可以當成dfs參數。

有時候備忘錄可能需要初始化一下。

  • 搞成全局的備忘錄里都是0,但是我們備忘錄里有一個規則,備忘錄里面初始存的值不能跟最終結果的值是一樣的。
  • 也就是說要去備忘錄找這個值的時候,我得先判斷一下你這個值是不是已經被計算過了
  • 如果這個備忘錄里面d(3)本來就存在X值,但是我還沒有進入過d(3)里呢,就可能會導致誤差。
  • 所有要把備忘錄初始化為這個dfs里永遠都不會出現得返回值!
class Solution {
public:int memo[31];int fib(int n) {memset(memo,-1,sizeof memo);//初始化return dfs(n);}int dfs(int n){//先 備忘錄里 瞅瞅if(memo[n]!=-1) return memo[n];//剪枝if(n<=1){memo[n]=n;//return 前就memo存一下return n;}memo[n]=dfs(n-1)+dfs(n-2);return memo[n]; }
};

解法三:動態規劃

動態規劃我們一般思路是盯著5個方向。

  • 確定狀態表示
  • 推導狀態轉移方程
  • 初始化
  • 確定填表順序
  • 確定返回值

解決動態規劃問題現創建一個表格。可能是一維的也可能是二維的。稱為dp表。

我們會賦予現賦予dp表一個含義。

如果有一個i下標,我們會賦予dp[i] 一個含義。

  • 其中這個dp[i]的含義就是狀態表示。
  • 推導這個狀態轉移方程就我想求這個dp[i]的值的時候,我們會從前面填的表格的值來推導dp[i]是多少。
  • 相當于是避免了重復做一件事情,實現了一個從前到后有邏輯的推導,進行線性的優化計算

具體推導處理的公式就是狀態轉移方程。

  • 初始化就是我們填dp[i]是依賴之前填的表格,但是0這個位置狀態沒有辦法搞。

因此必須先把0這個位置的值先初始化放后序填表。

  • 確定填表順序 如果填dp[i]狀態依賴于之前的狀態,就必須是從左到右。
  • 確定返回值 根據題目要求確定最后返回的是這個表中那一個數。

其實我們可以從之前的遞歸和記憶化搜索直接推出這5步,因為它們是一一對應的關系。

  • dfs函數頭就是給我一個數n我返回數n的斐波那契數。
  • 填寫備忘錄的順序,我們是做了深度優先遍歷因此會一直遞歸下去,所以先會把d(1),dp(0)先放到備忘錄里然后在往上返回一依次放。
  • 對應dp表就是從左到右。
  • 主函數是dfs(n)調用的,對應dp表返回的就是dp[n]的值。

為什么動態規劃和記憶化搜索這些都是一一對應的?

因為動態規劃和記憶化搜索本質都是一樣的:

  • 暴力求解(暴搜),動態規劃要求dp[i]也要把前面都算出來,也是暴搜。
  • 無非就是動態規劃和記憶化搜索是對暴搜的優化。

對暴力解法的優化是一樣的:把已經計算過的值,存起來。 記憶化搜索算d(5),因為d(4)和d(3)已經放進備忘錄里面了。

  • 直接去備忘錄里找拿d(4)和d(3)就看可以。
  • 動態規劃求dp[i]的時候是已經把前面dp[i-1]和dp[i-2]的值已經放到這個表里面了,然后求dp[i]的時候,直接去表里拿就就可以了。

算法導論》這本書就是把記憶化搜索和常規的動態規劃 歸為 動態規劃。

無法就是

  • 記憶化搜索是遞歸(借助 OS 棧來返回)形式的 動態規劃
  • 而 常規的動態規劃 是一個 遞推(循環) 存儲的 動態規劃。
class Solution {
public:int fib(int n) {//dp//方程//初始化//順序//返回值int dp[31];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.所有的遞歸(暴搜、深搜),都能改成記憶化搜索嗎?

不是的,只有在遞歸過程中,出現大量完全相同的問題時,才能用記憶化搜索的方式優化。

2.帶備忘錄的遞歸、帶備忘錄的動態規劃、記憶化搜索 都是一回事。

3.自頂向下 vs 自底向上 有什么不同

  • 無法就是解決一個問題時思考方向不同而已。
  • 自頂向下就是思考決策樹時是按照從上往下的順序來思考的,我想求d(5),我要先求出d(4)和d(3) 。。。。。
  • 自底向上就是從下往上思考,求d(5),我可以從最初開始看,由0 1 2推出 5

而這兩種方式就正好對應記憶化搜索和常規動態規劃。

  • 記憶化搜索是遞歸加一個備忘錄所以記憶化搜索方式就是從上往下。
  • 常規動態規劃是遞推方式,先求dp[0],自下往上對推導dp[n]是多少。

4.我們在解決這個問題的時候發現了一個流程

我可以先寫出暴搜,然后改成記憶化搜索,然后把記憶化搜索東西抽象處理就是動態規劃。

  • 好像發現解決動態規劃問題的全新流程暴搜->記憶化搜索->動態規劃。
  • 以前是 常規動態規劃。
  • 碰到一道動態規劃的題先寫成暴搜,然后改成記憶化搜索,在抽象成成動態規劃。

一般這樣搞也沒錯,但是有些題你直接寫出暴搜會比你用常規動態規劃成本高的多。

  • 暴搜->記憶化搜索->動態規劃 和 常規動態規劃 都是解決動態規劃的方式。
  • 那個更好, 因人而異,因題而異。
  • 在我看來暴搜可以為我們確定狀態表示,提供一個方向。

而且記憶化搜索就已經是一個動態規劃了,從暴搜改到記憶化搜索時間復雜度已經是線性級別了,沒有必要在搞成動態規劃了。

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

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

相關文章

云原生周刊:Kubernetes v1.33 要來了

開源項目推薦 Tekton Tekton 是一個開源的 K8s 原生 CI/CD 系統&#xff0c;它為構建、測試和部署自動化工作流提供了強大而靈活的框架。Tekton 提供了一套標準化的 API 和自定義資源&#xff08;CRDs&#xff09;&#xff0c;使得開發者能夠在 K8s 集群中定義和管理 CI/CD 管…

服務新增節點、遷移筆記

文章目錄 基礎配置部分基礎配置-hosts基礎配置-jdk包準備基礎配置-jdk環境變量配置基礎配置-skywalking包 基礎配置-apollo配置。 # 文件夾及配置基礎配置-tomcat基礎配置-nginx基礎配置部分-磁盤掛載(這個也差點漏掉)。 防火墻部分防火墻部分-數據庫及腳本防火墻部分-redis防火…

第十一章:Python PIL庫-圖像處理

一、PIL庫簡介 PIL&#xff08;Python Imaging Library&#xff09;是一個功能強大的圖像處理庫&#xff0c;它提供了豐富的圖像處理功能&#xff0c;包括圖像的打開、處理和保存等操作。PIL支持多種圖像文件格式&#xff0c;如JPEG、PNG、BMP等&#xff0c;并且可以完成對圖像…

【編譯、鏈接與構建詳解】Makefile 與 CMakeLists 的作用

【編譯、鏈接與構建詳解】Makefile 與 CMakeLists 的作用 前言源代碼&#xff08;.c、.cpp&#xff09;編譯編譯的本質編輯的結果編譯器&#xff08;GCC、G、NVCC 等&#xff09; 目標文件&#xff08;.o&#xff09;什么是 .o 目標文件為什么單個 .o 目標文件不能直接執行&…

Ubuntu / Debian 創建快捷方式啟動提權

簡述 在 Linux 系統中&#xff0c;.desktop 文件是 桌面入口文件&#xff0c;用于在桌面環境&#xff08;如 GNOME、KDE&#xff09;中定義應用程序的啟動方式、圖標、名稱等信息。當你執行 touch idea.desktop 時&#xff0c;實際上創建了一個空的 .desktop 文件&#xff08;…

ISIS報文

IS-IS 報文 目錄 IS-IS 報文 一、報文類型與功能 二、報文結構解析 三、核心功能特性 四、典型應用場景 五、抓包數據分析 六、總結 IS-IS&#xff08;中間系統到中間系統&#xff09;協議報文是用于鏈路狀態路由協議中網絡設備間交換路由信息的關鍵載體&#xff0c;其設…

beikeshop多商戶跨境電商獨立站最新版v1.6.0版本源碼

一.介紹 beikeshop跨境電商獨立站最新版V1.6.0源碼 多商戶 多商家 多語言 多幣結算 本博主親測搭建代碼全開源質量相對來說很穩定的 二.服務器環境 系統&#xff1a;CentOS、 環境&#xff1a;PHP7.4 Nginx 1.21 MySQL 5.6 常見插件&#xff1a;fileinfo &#xff1b; re…

Redis批量操作詳解

一、原生批量命令&#xff08;MSET&#xff09; 適用場景&#xff1a;所有鍵的過期時間相同或無過期設置&#xff0c;且無需條件判斷。 方法&#xff1a; 將多個SET命令合并為MSET命令&#xff0c;但需要注意MSET的局限性&#xff08;無法設置過期時間&#xff0c;且所有鍵值對…

Spring Boot 集成實戰:AI 工具如何自動生成完整微服務模塊

在數字化轉型的浪潮中&#xff0c;開發效率和質量是企業競爭力的關鍵要素。飛算 JavaAI 作為一款創新的 AI 工具&#xff0c;能在 Spring Boot 開發中&#xff0c;自動生成完整微服務模塊&#xff0c;極大提升開發效率。下面&#xff0c;我們就詳細介紹如何借助飛算 JavaAI&…

算法 | 2024最新算法:斑翠鳥優化算法原理,公式,應用,算法改進研究綜述,matlab代碼

基于斑翠鳥優化算法的原理、應用及改進研究綜述 一、算法原理 斑翠鳥優化算法(Pied Kingfisher Optimizer, PKO)是2024年由Bouaouda等人提出的一種新型仿生智能優化算法,其靈感來源于斑翠鳥的捕食行為與共生關系。算法通過模擬斑翠鳥的棲息懸停、潛水捕魚及與其他生物的共生…

RabbitMQ高級特性--重試特性

目錄 1.重試配置 2.配置交換機&隊列 3.發送消息 4.消費消息 5. 運行程序觀察結果 6. 手動確認 注意&#xff1a; 在消息傳遞過程中, 可能會遇到各種問題, 如網絡故障, 服務不可用, 資源不足等, 這些問題可能導致消息處理失敗. 為了解決這些問題, RabbitMQ 提供了重試機制, …

Vue 組件通信 - 中央事件總線

Vue 漸進式JavaScript 框架 基于Vue2的學習筆記 - Vue組件通信 - 中央事件總線 目錄 中央事件總線 圖示 準備工作 設置頁面元素 創建組件 總結 中央事件總線 使用vue的監聽和觸發來實現中央事件總線方式。 on監聽 emit觸發&#xff0c;組件按鈕綁定點擊事件&#xff0c…

5.0 WPF的基礎介紹1-Grid,Stack,button

WPF: Window Presentation Foundation. WPF與WinForms的對比如下&#xff1a; 特性WinFormsWPF技術基礎基于傳統的GDI&#xff08;圖形設備接口&#xff09;基于DirectX&#xff0c;支持硬件加速的矢量渲染UI設計方式拖拽控件事件驅動代碼&#xff08;簡單但局限&#xff09;…

QT軟件設計可考慮回答

在Qt應用中是否引入抽象類需要根據具體場景權衡&#xff0c;以下是分層建議&#xff1a; 建議采用抽象類的3個典型場景&#xff1a; 傳感器系統抽象&#xff08;強推薦&#xff09; class AbstractSensor { public:virtual ~AbstractSensor() default;virtual QVector<L…

pytorch學習(b站小土堆學習)

1 環境配置 參考鏈接 2. dir 和 help函數 dir()&#xff1a;用于查看某一模塊函數的方法 help()&#xff1a; 用于查看某方法的使用方法 3. dataset類實戰 利用Image對象打開圖片&#xff0c;利用os模塊的地址拼接組成圖片路徑 當我們用方括號訪問元素對象時&#xff0c;…

Unity TextMeshPro 實現文本逐字淡出效果

Unity TextMeshPro 實現文本逐字淡出效果 前言項目思路場景布置代碼編寫 前言 在處理角色對話時經常會用到一些文本動畫&#xff0c;正好記錄一下。使用 TextMeshPro&#xff0c;我們可以直接操作文本的頂點數據&#xff0c;實現諸如漸變、動畫等效果&#xff0c;為游戲界面和…

Mathtype無法插入到Word中

在word工具欄上有沒有出現Mtahtype&#xff0c;會出現以下兩種情況&#xff1a; 1. 沒有出現Mtahtype 2. 出現Mtahtype&#xff0c;但是點擊會出現彈窗 “ Couldnt find the MathPage.wll ” 解決方案 首先查看word版本是32位還是64位&#xff0c;這個位數是office安裝位數…

責任鏈模式_行為型_GOF23

責任鏈模式 責任鏈模式&#xff08;Chain of Responsibility Pattern&#xff09;是一種行為型設計模式&#xff0c;核心思想是將多個處理請求的對象連成一條鏈&#xff0c;請求沿鏈傳遞直到被處理。它像現實中的“多級審批流程”——請假或報銷時&#xff0c;申請會逐級提交給…

Qt圖形化界面為何總被“冷落“?

在Qt開發者的IDE中&#xff0c;Qt Designer總像一個被遺忘的角落——即便它有著直觀的拖拽式界面設計功能。通過分析GitHub上超過5000個Qt項目發現&#xff0c;僅有17%的項目使用.ui文件構建界面。這個數據背后&#xff0c;隱藏著開發者群體對GUI構建方式的集體選擇。我們不禁要…

SQL Server從安裝到入門一文掌握應用能力。

本篇文章主要講解,SQL Server的安裝教程及入門使用的基礎知識,通過本篇文章你可以快速掌握SQL Server的建庫、建表、增加、查詢、刪除、修改等基本數據庫操作能力。 作者:任聰聰 日期:2025年3月31日 一、SQL Server 介紹: SQL Server 是微軟旗下的一款主流且優質的數據庫…