前言:
最近刷完leetcode遞迴的專題了,無奈本人很菜,關於遞迴每次都是看大佬的題解,自己也設計不出來遞迴,今日打算從遞迴本質出發,徹底剖析遞迴。本文中的大部分遞迴思想來自:遞迴的內涵與經典應用。
在數學與電腦科學中,遞迴(recursion)是指在函式的定義中使用函式自身的方法。實際上,遞迴,顧名思義,其包含了兩個意思:遞
和歸
,這正是遞迴思想的精華所在。
遞迴就是分為遞去
和歸來
,遞去是指:遞迴的問題必須可以分解為若干規模較小,與原問題相同的子問題,這些子問題可以用相同的解題思路解決;歸來是指:這些問題的演化過程是乙個從小到大、由遠及近的過程,並且會有乙個明確的終點,一旦到了這個明確的終點後,就需要從原路返回到原點了(模擬迷宮的分叉點),原問題就能解決了。
更直接地說,遞迴的基本思想就是把規模大的問題轉化為規模小的相似的子問題來解決。特別地,在函式實現時,因為解決大問題的方法和解決小問題的方法往往是同乙個方法,所以就產生了函式呼叫它自身的情況,這也正是遞迴的定義所在。格外重要的是,這個解決問題的函式必須有明確的結束條件,否則就會導致無限遞迴的情況。數學歸納法用於將解決的原問題轉化為解決它的子問題,而它的子問題的子問題,和原問題其實都是乙個模型,也就是說
存在相同的邏輯歸納處理項
。當然遞迴結束的最後乙個子問題不是我們的邏輯歸納項,否則我們就要進行無窮遞迴了。
數學歸納法三個關鍵要素:模板一:在遞去中解決問題(回溯法模板)
function recursion
(大規模)
else
}
模板二:在歸來的過程中解決問題(分治法模板)
function recursion
(大規模)
else
}
通常情況下,遞迴是一種直觀而有效的實現演算法的方法。 但是,如果我們不明智地使用它,可能會給效能帶來一些不希望的損失,例如重複計算。 這時我們就需要使用一種稱之為記憶術(memoization)
的方法,來避免這個問題。
比如:計算裴波那契數,f(n) = f(n - 1) + f(n - 2),f(0) = 0, f(1) = 1為了消除上述情況中的重複計算,正如許多人已經指出的那樣,其中乙個想法是將中間結果儲存在快取中,以便我們以後可以重用它們,而不需要重新計算,這就是所謂的記憶術。下面這個數顯示了在計算f(4)時發生的所有重複計算(按顏色分組)
記憶化:
是一種優化技術,主要用於加快電腦程式的速度,方法是儲存昂貴的函式呼叫的結果,並在相同的輸入再次出現時返回快取的結果。
對於上述計算斐波那契數列中的重複項計算,我們使用乙個hashmap來存放的鍵值對就好了,再遇到重複項時,我們直接返回這個重複項就好了,不需要重複計算。尾遞迴:尾遞迴函式是遞迴函式的一種,其中遞迴呼叫是遞迴函式中的最後一條指令。並且在函式中應該只有一次遞迴呼叫。區別尾遞迴與非尾遞迴最重要到一點就是最後一次遞迴呼叫中是否有額外的計算。
尾遞迴的好處:
它可以避免遞迴呼叫期間棧空間開銷的累積,因為系統可以為每個遞迴呼叫重用棧中的固定空間。具體可參考:這裡。
在學習和生活中,遞迴演算法一般用於解決三類問題:
遞迴與迴圈是兩種不同的解決問題的典型思路。遞迴通常很直白地描述了乙個問題的求解過程,因此也是最容易被想到解決方式。迴圈其實和遞迴具有相同的特性,即做重複任務,但有時使用迴圈的演算法並不會那麼清晰地描述解決問題步驟。單從演算法設計上看,遞迴和迴圈並無優劣之別。然而,在實際開發中,因為函式呼叫的開銷,遞迴常常會帶來效能問題,特別是在求解規模不確定的情況下;而迴圈因為沒有函式呼叫開銷,所以效率會比遞迴高。遞迴求解方式和迴圈求解方式往往可以互換,也就是說,如果用到遞迴的地方可以很方便使用迴圈替換,而不影響程式的閱讀,那麼替換成迴圈往往是好的。
問題的遞迴實現轉換成非遞迴實現一般需要兩步工作:
【型別3:資料結構是遞迴的】:344. 反轉字串
void
reversestring
(vector<
char
>
& s)
void
reversestring
(vector<
char
>
& s,
int i,
int j)
【型別3:資料結構是遞迴的】:206.反轉鍊錶:
listnode*
reverselist
(listnode *head)
【型別1:問題的定義就是按遞迴定義的】70.爬樓梯:
int
climbstairs
(int n)
;return
helper(0
,n,memo);}
inthelper
(int i,
int n,
int* memo)
【型別3:資料結構是遞迴的】:leetcode104:二叉樹的最大深度
int
maxdepth
(treenode* root)
【型別3:資料結構是遞迴的】:leetcode21:合併兩個有序鍊錶
listnode*
mergetwolists
(listnode* l1, listnode* l2)
else
return head;
}
【型別3:資料結構是遞迴的】:leetcode95:不同的二叉搜尋樹ⅱ
vector
>
generatetrees
(int n)
;return
helper(1
,n);
}
vector
>
helper
(int begin,
int end)
for(
int i=begin;i<=end;
++i)}}
return result;
}
注:遞迴題持續更新,未完待續~ LeetCode刷題總結
123 4567 891011 12 元素交換 swap a 1 a 3 sort排序 sort a.begin a.end 陣列顛倒 reverse a.begin a.end 陣列元素置為0 memset a,0,a.size 陣列取值 a.push back 定義二維陣列 vector vec...
LeetCode刷題總結
123 4567 891011 12 元素交換 swap a 1 a 3 sort排序 sort a.begin a.end 陣列顛倒 reverse a.begin a.end 陣列元素置為0 memset a,0,a.size 陣列取值 a.push back 定義二維陣列 vector vec...
leetcode刷題總結之滑動視窗
前言 這段時間課比較多,還要準備6級,所以題刷的有點慢。我把leetcode關於滑動視窗的免費題差不多都做了,現在總結一下套路,供以後複習用。滑動視窗思想 視窗由兩個指標構成,乙個左指標left,乙個右指標right,然後 left,right 表示的索引範圍是乙個視窗了。右指標right的功能是用...