主頁 > 軟體設計 > 【演算法】用習題教你如何使用動態規劃,超詳細,一看就會!!建議收藏!!

【演算法】用習題教你如何使用動態規劃,超詳細,一看就會!!建議收藏!!

2021-08-15 08:05:23 軟體設計

從0到1的動態規劃

  • 前言
  • 一、為何要使用動態規劃?
    • 斐波那契
  • 二、最大連續的子陣列和
  • 三、LC12 拆分詞句
  • 四、三角形
  • 五、LC88 求路徑
  • 六、LC87 求路徑 ii
  • 七、 背包問題
  • 總結

只要能堅持看完,我相信你一定能學到!!


前言

動態規劃(DP)的定義:動態規劃是分治思想的延伸,通俗一點來說就是大事化小,小事化無的藝術,
動態規劃具備了以下三個特點:

  1. 把原來的問題分解成了幾個相似的子問題,
  2. 所有的子問題都只需要解決一次,
  3. 儲存子問題的解

并且我們通常從以下的四個角度來出發(重要)

  1. 狀態定義
  2. 狀態間的轉移方程定義
  3. 狀態的初始化
  4. 回傳結果

一、為何要使用動態規劃?

我們首先看這樣一道題目

在這里插入圖片描述

斐波那契

點擊鏈接跳轉:斐波那契數列

同學們可能在初始學習C語言,學到遞回的時候肯定少不了 這道經典的題目,大家通常也都是用遞回的方式去完成,但是我們發現一點,當我們用遞回實作斐波那契數的演算法是2^n的時間復雜度,數字給的大的時候就求不出來了,這時我們就會用到動態規劃的思想
廢話不多說,現在我們來看看按照我們上面給的邏輯如何用我們的四個步驟完成這道題目

狀態的定義:求F(n)的值
狀態方程的轉換:F(n)=F(n-1)+F(n-2)
初始化: F(0) =0;F(1)=1
回傳結果:F(n)

C++實作:
class Solution {
public:
    int Fibonacci(int n) {
        vector<int> v;
        //初始化
        v.push_back(0);
        v.push_back(1);
        //狀態的轉移方程
        for(int i=2;i<=n;i++)
        {
           int ret  = v[i - 1] + v[i - 2];
            v.push_back(ret);
        }
        //回傳結果
        
        return v[n];
    }
};

二、最大連續的子陣列和

最大連續的子陣列和
在這里插入圖片描述

int FindGreatestSumOfSubArray(vector<int> array) 

這里我們就該思考他的狀態如何定義,假設我們這里用前i個元素的陣列的最大和作為狀態的定義,我們會發現,我們無法利用前一個狀態,做出變化轉換到下一個狀態,
我們這里的狀態定義:以第i個元素為結尾的最大的子陣列和
原因:我們在新增元素進去時,我們就可以根據新增元素是否需要加入進去并且保持是一個連續的子陣列和
狀態遞推:F[i] = max(F[i-1]+array[i-1],array[i-1]);意思即上一個最大值是否加入當前值與該元素本身比較后將大的放入F(i)中
結果:回傳我們陣列當中最大的值即是在這個陣列當中的最大的子陣列和

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int sz = array.size();
        vector<int> F(sz+1);
        //初始化
        F[1] = array[0];
        for(int i =2;i<=sz;i++)
        {
            //遞推方程:以i結尾的子陣列最大和
            F[i] = max(F[i-1]+array[i-1],array[i-1]);
        }
        int maxI = F[1];
        for(int i =2 ;i<= sz;++i)
        {
            maxI = max(F[i],maxI);
        }
        return maxI;
    }
};

三、LC12 拆分詞句

LC12 拆分詞句

bool wordBreak(string s, unordered_set<string> &dict) 

分析題意:就是要我們在給定的字串中判斷是否能將字串分割成字典dict中能找到的值
狀態定義: 前i個字符能否在dict中查找到
**狀態轉換:**這里先解釋一點,狀態的轉換不一定是一個等式,比如在這個地方,定義為,j<i && F(j) && (j+1,i) 為dict中單詞,解釋一下,就是我們要利用前面j個字符的狀態,當前面的j個字符為在dict中能中能找到,即F(j)為真,我們只需要在判斷(j+1,i)這個區間也是在dict當中能查找的字串就能保證整體為真了!(這里的F(j)是兩個或多個字符組成的都沒關系,在dict當中能找到就表明他是真
初始化:我們這里的起始狀態可以給一個"",表示空串,在這個的基礎上的狀態轉換方程表示的是,字串整體能在dict中查找到,即將F[0] 置成ture

class Solution {
public:
    bool wordBreak(string s, unordered_set<string> &dict) {
// 前j個字符能在dict中查找
// 狀態方程:j<i && f[j] && [j+1,i]組成字符
// 特殊情況:第一個字符
// F[0] ture, F[1] --要把F[0]弄成輔助狀態,這樣表示前j個字符為整體的時候在dict中能查找
        int n = s.size();
        vector<bool> canBreak(n+1 ,false);//前i個字符 -- 映射下標位置
        canBreak[0]=true;//這個是輔助狀態
        for(int i= 1; i <= n;i++)//處理第一個到第n個字符
        {
            for(int j =i-1 ; j >= 0 ;j--)
            {
               if(canBreak[j] && dict.find(s.substr(j,i-j))!=dict.end())// 狀態方程:j<i && f[j] && [j+1,i]組成字符
               {
                   canBreak[i] =true;
               }
            }
        }
        return canBreak[n];
    }
};

這題的狀態轉換其實就沒有之前的兩道題好想了,但是我們在思考的時候能夠想到狀態的轉換問題其實就已經解決了,所以很多時候都是只差臨門一腳


四、三角形

跳轉鏈接:三角形
在這里插入圖片描述

int minimumTotal(vector<vector<int> > &triangle) 

像題目所給的這種我們很容易就能判斷出來結果,但如果我們將第三行的70改成1的話我們答案會如何呢
在這里插入圖片描述
在這里插入圖片描述
下圖演示的例子:下圖演示的例子
初始化的例子:在這里插入圖片描述

分析:觀察上圖的兩張表,我們可以看出,要求出從上到下的最小和,并且一次移動只能移動到從(i,j)–> (i+1,j),(i+1,j+1) ,很明顯我們不能就直接選擇下一行的最小值,所以我們要個二維陣列去保存所有的子陣列的和F(i,j)
狀態定義:到第i行的最小值
狀態轉換:以圖中的50分析:因為當前行的大小是由 F(i,j) -->
min(F(i-1,j),F(i-1,j-1))+(i,j),即我們可以保存從上面下來的所有情況的最小和,例如:50這個位置就是從20 -->30–>50,所以50對應的二維陣列存放這100-- 這樣說應該就能理解了
初始化:可以從上面的圖看出,當 j== 0時,即第一列和當 i==j時,對角線他們的可能只能是從固定位置下來的,所以我們初始化的時候先將這些值置成對應的值
解題:我的解題當中所用的是直接在他們給我們的二維陣列triangle中更新,因為我們遍歷陣列的時候剛好可以就將值更新好放回去

class Solution {
public:
    int minimumTotal(vector<vector<int> > &triangle) {
//         直接用triangle二維陣列記錄當前位置的最小值

// 初始化第0列的應該加上上一行的值加上這一行的值,i==j的就可以上一(i-1,j-1)的值加上當前值,其他的位置就可以是上一行的和上一行上一列的最小值加上當前值
        int x =triangle.size();
        for(int i =1;i<x;i++)
        {
            for(int j=0;j<i+1;j++)
            {
                if(j==0)
                    triangle[i][j]=triangle[i-1][j]+triangle[i][j];
                else if(i == j)
                {
                    triangle[i][j]=triangle[i-1][j-1]+triangle[i][j];
                }
                else
                {
                    triangle[i][j]=triangle[i][j]+min(triangle[i-1][j-1],triangle[i-1][j]);
                }
            }
        }
        int y =triangle[x-1].size();
        int minret= triangle[x-1][0];
        for(int i=1;i<y;++i)
        {
            minret = min(minret,triangle[x-1][i]);
        }
        return minret;
    }
};

五、LC88 求路徑

LC88 求路徑
在這里插入圖片描述

決議:這道題的題意非常簡單,從起點到終點求路徑,且每次只能往下或者往右
在這里插入圖片描述
根據上圖:其實每個點路徑和是上一行和左一列到該位置的路徑和相加,所以我們用一個二維陣列去保存子答案的結果
狀態定義: 從(0,0)到(i,j)的路徑數
狀態轉換:F(i,j) =F(i-1,j)+F(i,j-1) – 即每個點的路徑和個數為上一行和左邊一列的和相加
初始化:觀察到第一行和第一列,他們都只有一種可能性,一直往右走,和一直往下走,所以初始化的時候我們將他們全部置成1;
回傳:F(m-1,n-1)

class Solution {
public:
    /**
     * 
     * @param m int整型 
     * @param n int整型 
     * @return int整型
     */
    int uniquePaths(int m, int n) {
        // write code here
        //狀態轉換 --(i,j)是(i-1,j)與(i,j-1)走多一步得到的
        vector<vector<int>> retArr(m,vector<int>(n,0));
        retArr[0][0]=1;
        for(int i =1;i<m;++i)
        {
            
            retArr[i][0]=1;//處理第一列
        }
        for(int i =1;i<n;++i)
        {
            retArr[0][i]=1;//處理第一行
        }
        for(int i = 1;i<m;++i)
        {
            for(int j =1;j<n;++j)
            {
                retArr[i][j] =retArr[i-1][j]+retArr[i][j-1];
            }
        }
        return retArr[m-1][n-1];
    }
};

六、LC87 求路徑 ii

LC87 求路徑 ii
在這里插入圖片描述

分析:比起上一題這里就是添加了障礙,在有障礙的地方我們置成路徑和為0的話,那么結論每個點路徑和是上一行和左一列到該位置的路徑和相加,所以我們用一個二維陣列去保存子答案的結果在這里也是適用的

狀態定義: 從(0,0)到(i,j)的路徑數
狀態轉換:F(i,j) =F(i-1,j)+F(i,j-1) – 即每個點的路徑和個數為上一行和左邊一列的和相加
初始化:初始化我們將第一行,第一列有路障的后面置0,并且(0,0)如果是有路障或者終點處有路障直接就回傳0
技巧: 我們可以將我們的二維陣列提前置成0,這樣初始化遍歷的時候我們break跳出回圈相當于也將后面的初始化完成了,具體看下面的代碼吧!!!

class Solution {
public:
    /**
     * 
     * @param obstacleGrid int整型vector<vector<>> 
     * @return int整型
     */
    int uniquePathsWithObstacles(vector<vector<int> >& obstacleGrid) {
        // write code here
//         到(i,j)!=1的時候就有
// 也等于(i-1,j) 和(i,j-1)
// 初始化的時候0,0 為1直接回傳
// 初始化之后我們將有障礙的都置成0,這樣我們就可以套用上題的思路
        if(obstacleGrid.empty())
            return 0;
        int x =obstacleGrid.size();
        int y =obstacleGrid[0].size();
        if(obstacleGrid[0][0]||obstacleGrid[x-1][y-1])
            return 0;
        vector<vector<int>> retArr(x,vector<int>(y,0));//置成0方便后續我們操作
        for(int i=0;i<x;i++)
        {
            if(obstacleGrid[i][0] == 1)
                break;//有障礙的話我們就直接退出
            retArr[i][0]=1;
        }
        for(int i=0;i<y;i++)
        {
            if(obstacleGrid[0][i] == 1)
                break;
            retArr[0][i]=1;
        }
        for(int i =1;i<x;i++)
        {
            for(int j =1;j<y;j++)
            {
                if(obstacleGrid[i][j]==0)
                retArr[i][j] = retArr[i-1][j]+retArr[i][j-1];
                else
                retArr[i][j]= 0;
            }
        }
        return retArr[x-1][y-1];
    }
};

相似的題目:LC86 帶權值的最小路徑和,自己也動手試試吧


七、 背包問題

背包問題

> 這里是參考

如果看到這題的時候,你已經在能夠自己推導這道題的狀態定義和轉換方程,再來聽聽我講的,那么這道題可能會比較容易吸收

狀態:
F(i, j): 前i個物品放入大小為j的背包中所獲得的最大價值,如果不將j置成變數的話前i個物品放入背包中所獲得的最大價值,是沒有辦法判斷當前背包大小是否夠放當前物品的 –
狀態遞推:對于第i個商品,有一種例外,裝不下,兩種選擇,放或者不放
如果裝不下:此時的價值與前i-1個的價值是一樣的
F(i,j) = F(i-1,j),如果可以裝入:需要在兩種選擇中找最大的
F(i, j) =max(F[i-1][j-A[i-1]]+V[i-1],F[i-1][j])
F(i-1,j): 表示不把第i個物品放入背包中, 所以它的價值就是前i-1個物品放入大小為j的背包的最大價值
F(i-1, j - A[i]) + V[i]:表示把第i個物品放入背包中,價值在該物品價值的基礎上增加V[i],但是需要騰出j - A[i]的大小放第i個商品

我們以題目的情況進行分析
初始化:在這里插入圖片描述

在這里插入圖片描述

這里對2這個點單獨分析一下,后面的也跟這個類似,當我們考慮是否要放2的時候,我們要考慮放完之后的剩余容量j-A[i-1] ,這里值為0,所以我們加上上一行背包大小為0時的值,即當前的最大值
max(vv[i-1][j-A[i-1]]+V[i-1],vv[i-1][j])
下面再給一個例子

在這里插入圖片描述


總結

動態規劃的題要自己多做做,這些題都得做做,做的多了自然就會有思路了,下次我們講講回文串分割,編輯問題和不同的子序列,這節的知識如果沒看懂的歡迎來與我討論,制作不易,一鍵三連!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/293775.html

標籤:其他

上一篇:標準對話框

下一篇:【智力題】有 1000 瓶藥物,但是其中有一瓶是有毒的,小白鼠吃了一個星期以后就會死掉!請問,在一個星期內找出有毒的 藥物,最少需要多少只小白鼠?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more