算法基礎(chǔ)--遞歸和動態(tài)規(guī)劃

本文主要作為自己的學(xué)習(xí)筆記黍匾,并不具備過多的指導(dǎo)意義。

暴力遞歸

  1. 把問題轉(zhuǎn)化為規(guī)目遄浚縮小了的同類問題的子問題

  2. 有明確的不需要繼續(xù)遞歸的條件

    base case


求n!的結(jié)果

非遞歸版本

從非依賴關(guān)系入手哎迄。明確的知曉n!=1×2×3×...×n,然后按照順序編寫算法即可

func getFactorial1(n : Int) -> Int {
    var res = 1
    for i in 1..<n+1 {
        res = res * i
    }
    
    return res
}

遞歸版本

從依賴關(guān)系入手境钟。n已知,嘗試解決(n-1)!

func getFactorial2(n : Int) -> Int {
    if n == 1 {
        return 1
    }
    return n * getFactorial2(n: n-1)
}

漢諾塔問題

打印N層漢諾塔從最左邊移動到最右邊的全部過程

每次一個俭识,不能打壓小只能小壓大

image

在第N層的問題上慨削,需要完成以下三個狀態(tài):

第N層的完成依賴N-1的完成,而第N-1層的完成又依賴N-1層的完成套媚。

image
/// 移動1-N層漢諾塔
///
/// - Parameters:
///   - n: 需要移動到的層數(shù)
///   - form: 從哪根開始
///   - to: 從哪根結(jié)束
///   - help: 空那根
func hanoiGame(n : Int ,form :String ,to :String ,help :String) {
    if n == 1 {//只移動第一層缚态,直接移動即可
        print("Move 1 from " + form + " to " + to)
    }else {
        hanoiGame(n: n-1, form: form, to: help, help: to)  //將第 1到n-1 層移動到 中間
        print("Move \(n) " + "from " + form + " to " + to) //將第 n 層移動到 最右
        hanoiGame(n: n-1, form: help, to: to, help: form) //將第 1到n-1 層移動到 最右
    }
}



hanoiGame(n: 3, form: "左", to: "右", help: "中")
//打印
Move 1 from 左 to 右
Move 2 from 左 to 中
Move 1 from 右 to 中
Move 3 from 左 to 右
Move 1 from 中 to 左
Move 2 from 中 to 右
Move 1 from 左 to 右

打印字符串能組成的所有字串

輸入abc
打印:abc堤瘤,ab玫芦,ac,a本辐,bc桥帆,b,c

將字符串轉(zhuǎn)化成數(shù)組师郑,每個位置都有兩個選擇:打印&&跳過环葵。以此遞歸

代碼

func printStr(str :String) {
    printAllSub(str: wordToArr(word: str), i: 0, res: "")
}

func printAllSub(str :[String] ,i :Int ,res :String) {
    if i == str.count {
        print(res)
    }else {
        printAllSub(str: str, i: i+1, res: res+str[i]) //打印當(dāng)前位置
        printAllSub(str: str, i: i+1, res: res) //不打印當(dāng)前位置
    }

}

func wordToArr(word:String) -> Array<String> {
    var res : [String]
    res = Array.init()
    if word.count == 0 {
        return res
    }
    let string = (word as NSString)
    for i in 0..<string.length {
        res.append(string.substring(with: NSMakeRange(i, 1)))
    }
    
    return res
}


母牛數(shù)目問題

有一頭母牛,它每年年初生一頭小母牛宝冕。每頭小母牛從第四個年頭開始张遭,每年年初也生一頭小母牛。請編程實現(xiàn)在第n年的時候地梨,共有多少頭母牛菊卷?

當(dāng)思維不夠直觀的時候,不妨列舉一下試試查找規(guī)律

image

F(N) = F(N-1) + F(N-3)

第五年 = 第四年存活的 + A與第二年出生的B所生的兩個

需要注意:如果N-3為負數(shù)則不用計算宝剖,只計算母牛自己生的一個即可

func func(n : Int) -> Int {
    if n == 1 {
        return 1
    }
    if n - 3 <= 0 {
        return func1(n: n-1) + 1
    }else {
        return func1(n: n-1) + func1(n: n-3)
    }
}

二維數(shù)組--從左上角到右下角最大值

只能向右或向下走

經(jīng)典的動態(tài)規(guī)劃題目洁闰,但我們可以先從遞歸做起

/// 二維數(shù)組--從左上角到右下角最大值
///
/// - Parameters:
///   - matrix: 二維矩陣
///   - x: x軸坐標(biāo)
///   - y: y軸坐標(biāo)
/// - Returns: 當(dāng)前點到右下角最小距離
func walk(matrix : [[Int]] ,x :Int ,y :Int) -> Int {
    if (x == matrix.count-1) && (y == matrix[0].count-1) { //已經(jīng)到最后
        return matrix[x][y] //返回當(dāng)前節(jié)點
    }
    
    if x == matrix.count-1 {  //已經(jīng)到x軸末尾
        return matrix[x][y] + walk(matrix: matrix, x: x, y: y+1) //當(dāng)前節(jié)點+y軸下一位
    }
    
    if y == matrix[0].count-1 { //已經(jīng)到y(tǒng)軸末尾
        return matrix[x][y] + walk(matrix: matrix, x: x+1, y: y) //當(dāng)前節(jié)點+x軸下一位
    }
    
    //當(dāng)前節(jié)點+min(x軸下一位,y軸下一位)
    return matrix[x][y] + min(walk(matrix: matrix, x: x+1, y: y), walk(matrix: matrix, x: x, y: y+1))
}

暴力遞歸的弊端

第一次進入walk(0,0)時,將會遞歸調(diào)用藍色位置walk(1,0)walk(0,1)万细。

image

而在進入walk(1,0)時扑眉,又將遞歸調(diào)用walk(2,0)walk(1,1)
并且進入walk(0,1)時,又將遞歸調(diào)用walk(0,2)walk(1,1)

image

此時walk(1,1)將會執(zhí)行兩次赖钞,其之后的遞歸計算也指數(shù)級的重復(fù)腰素。

這就是動態(tài)規(guī)劃的意義,解決暴力遞歸重復(fù)執(zhí)行的缺點進行優(yōu)化


動態(tài)規(guī)劃

所有的動態(tài)規(guī)劃雪营,都是從暴力遞歸嘗試優(yōu)化(減少重復(fù)計算)而來

面試中弓千,對于一個沒有見過的動態(tài)規(guī)劃。我們可以先寫出一個遞歸的嘗試版本献起,在驗證正確性之后嘗試改成動態(tài)規(guī)劃洋访。

遞歸方法的后效性

如上文中所提到的暴力遞歸的弊端一樣:有些暴力遞歸會存在重復(fù)狀態(tài)镣陕,并且這些重復(fù)狀態(tài)的結(jié)果與到達其的路徑無關(guān)(狀態(tài)的參數(shù)確定,返回值則確定)姻政。

什么樣的問題可以改成動態(tài)規(guī)劃

對于無后效性遞歸呆抑,可以改成動態(tài)規(guī)劃的版本。

也有反例:比如漢諾塔問題扶歪,每一步打印都會對整體的打印結(jié)果造成影響理肺。就叫有后效性遞歸摄闸,無法進行動態(tài)規(guī)劃善镰。

無后效性遞歸如何改成動態(tài)規(guī)劃的通用方法

二維數(shù)組--從左上角到右下角最大值題目為例:

  1. 分析可變參數(shù),建立狀態(tài)表

    以每個狀態(tài)的return結(jié)果建立一個二維數(shù)組年枕。

  2. 找到自己需要的最終狀態(tài)位置(0,0)


    image
  3. 回到base case 中炫欺,對不被依賴的位置進行設(shè)置


    image
  4. 對普遍位置進行設(shè)置


    image
  5. 最終得到目標(biāo)位置


數(shù)組中元素是否能組成指定的和

先寫一個正常的暴力遞歸嘗試版本,與之前打印字符串能組成的所有字串的問題基本一致

/// 數(shù)組中元素是否能組成指定的和
///
/// - Parameters:
///   - arr: 數(shù)組
///   - i: 當(dāng)前位置
///   - sum: 已經(jīng)求的和
///   - aim: 目標(biāo)和
/// - Returns: 結(jié)果
func isSum(arr :[Int] ,i :Int ,sum :Int ,aim :Int) -> Bool {
    if i == arr.count { //數(shù)組末尾已經(jīng)嘗試結(jié)束
        return aim==sum //直接比對
    }
    
    let useC = isSum(arr: arr, i: i+1, sum: sum+arr[i], aim: aim) //嘗試添加當(dāng)前位置
    
    let unuseC = isSum(arr: arr, i: i+1, sum: sum, aim: aim) //不添加當(dāng)前位置
    
    return useC || unuseC
}

如何轉(zhuǎn)變成動態(tài)規(guī)劃

  1. 簡化表達式熏兄,并建立動態(tài)規(guī)劃表

    只有兩個可變參數(shù)品洛,可以簡化成F(i,sum)

    DP表的設(shè)計行為sum(最后一位為所有元素之和),列為i摩桶。
    在代碼上桥状,將作為一個二維數(shù)組存在


    image
  2. 確定目標(biāo)位置


    image
  3. base case中找到不被依賴的位置
    只有在F(N,Aim)時,aim==sum才會返回true

    image

  4. 對普遍位置進行設(shè)置
    某一個位置F(i,sum1)的狀態(tài)依賴于F(i+1,sum1)F(i+1,sum1)+arr[i]
    F(i+1,sum1)+arr[i]又作為新的sum值Sum2存在于DP表內(nèi)硝清。
    兩個位置有一個為Aim辅斟,則將返回true

    image

  5. 推回到最初位置


參考資料

左神牛課網(wǎng)算法課

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芦拿,隨后出現(xiàn)的幾起案子士飒,更是在濱河造成了極大的恐慌,老刑警劉巖蔗崎,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酵幕,死亡現(xiàn)場離奇詭異,居然都是意外死亡缓苛,警方通過查閱死者的電腦和手機芳撒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來未桥,“玉大人笔刹,你說我怎么就攤上這事「质簦” “怎么了徘熔?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淆党。 經(jīng)常有香客問我酷师,道長讶凉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任山孔,我火速辦了婚禮懂讯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘台颠。我一直安慰自己褐望,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布串前。 她就那樣靜靜地躺著瘫里,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荡碾。 梳的紋絲不亂的頭發(fā)上谨读,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天,我揣著相機與錄音坛吁,去河邊找鬼劳殖。 笑死,一個胖子當(dāng)著我的面吹牛拨脉,可吹牛的內(nèi)容都是我干的哆姻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼玫膀,長吁一口氣:“原來是場噩夢啊……” “哼矛缨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起匆骗,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤劳景,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碉就,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盟广,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年瓮钥,在試婚紗的時候發(fā)現(xiàn)自己被綠了筋量。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡碉熄,死狀恐怖桨武,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锈津,我是刑警寧澤呀酸,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站琼梆,受9級特大地震影響性誉,放射性物質(zhì)發(fā)生泄漏窿吩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一错览、第九天 我趴在偏房一處隱蔽的房頂上張望纫雁。 院中可真熱鬧,春花似錦倾哺、人聲如沸轧邪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忌愚。三九已至,卻和暖如春扣猫,著一層夾襖步出監(jiān)牢的瞬間菜循,已是汗流浹背翘地。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工申尤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衙耕。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓昧穿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親橙喘。 傳聞我的和親對象是個殘疾皇子时鸵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容