重溫算法Day7:遞歸

遞歸是一種應用非常廣泛的算法
遞歸需要滿足的三個條件
1. 一個問題的解可以分解爲幾個子問題的解
2. 這個問題與分解之後的子問題,除了數據規模不同,求解思路完全一樣
3. 存在遞歸終止條件

寫遞歸代碼最關鍵的是寫出遞推公式,找到終止條件

示例:
有 n 個臺階,每次你可以跨 1 個臺階或者 2 個臺階,請問走這 n 個臺階有多少種走法?
分析:
n=1時,1種;
n=2時,2種;
n>2時,第一步走1個臺階,則有f(n-1)種;第一步走2個臺階,則有f(n-2)種;
所以 f(n) = f(n-1) + f(n-2) 

func f(n int) int {
  if n == 1 {
    return 1
  } 
  if n == 2 {
    return 2
  } 
  return f(n-1) + f(n-2)
}

寫遞歸代碼的關鍵就是找到如何將大問題分解爲小問題的規律,並且基於此寫出遞推公式,然後再推敲終止條件,最後將遞推公式和終止條件翻譯成代碼。

注意點:

1.要警惕堆棧溢出,限制遞歸調用的最大深度.
2.要警惕重複計算。通過一個數據結構(比如散列表)來保存已經求解過的 f(k)。當遞歸調用到 f(k) 時,先看下是否已經求解過了。

例題:

https://leetcode-cn.com/problems/climbing-stairs/

func climbStairs(n int) int {
    //方法2
    // var tempMap = make(map[int]int)
    // return climb2(0, n, tempMap)

    //方法1,3,4
    return climb4(n)
}

//dp原理:dp[i] = dp[i-1] + dp[i-2]

//方法1 暴力法
func climb1(i, n int) int {
	if i == n {
		return 1
	}

	if i > n {
		return 0
	}

	return climb1(i+1, n) + climb1(i+2, n)
}

//方法2 記憶
func climb2(i, n int, tempMap map[int]int) int {
	if i == n {
		return 1
	}

	if i > n {
		return 0
	}

    if tempMap[i] == 0 {
        tempMap[i] = climb2(i + 1, n, tempMap) + climb2(i + 2, n, tempMap)
    }

	return tempMap[i]
}

//方法3 動態規劃
func climb3(n int) int {
    var tempMap = make(map[int]int)
    tempMap[1] = 1
    tempMap[2] = 2
    for i := 3; i <= n; i++ {
        tempMap[i] = tempMap[i-1] + tempMap[i-2]
    }

    return tempMap[n]
}

//方法4 動態規劃 + 優化
func climb4(n int) int {
    if n == 1 {
        return 1
    }
    one, two := 1, 2
    for i := 3; i <= n; i++ {
        one, two = two, one + two
    }

    return two
}

因爲遞歸調用一次就會在內存棧中保存一次現場數據,所以在分析遞歸代碼空間複雜度時,需要額外考慮這部分的開銷.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章