遞歸算法是爲了描述問題的某一狀態,必須用到該狀態的上一狀態,而描述上一狀態,又必須用到上一狀態的上一狀態……這種用自已來定義自己的方法,稱爲遞歸定義。比如最出名的一個問題,求斐波那契數列的第i位,如果用遞歸算法做,就需要不斷遞歸得到前兩位的值。
而回溯算法的本質是爲了得到可能存在的所有情況,當一個分支走到底之後,就返回頂點繼續遍歷下一個分支。在這個返回頂點的過程,就需要不斷調用自身函數,這裏的調用自身函數是爲了窮舉所有可能的情況。
之前對遞歸的理解有點狹隘,認爲只要是函數調用自身就算遞歸。所以對於回溯算法來說,回溯的過程函數也會調用自身,就容易將回溯與遞歸混淆。當然,對於有的特殊的問題,這兩者可能沒有明確的界限。
我們來具體地看一到題,用遞歸和回溯兩種方法解答,來看看遞歸和回溯的區別:
子集
給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
遞歸:
首先我們來看遞歸的做法。假設要求數組[1,2,3]的子集,我們需要先求得[1,2]的子集A1,然後向[1,2]子集的每個元素裏添加3這個元素,得到的新子集A2,再加上原來的子集A1,即爲[1,2,3]的子集。
所以我們開始假設輸出子集爲空,每一步都向子集添加新的整數,並生成新的子集。
c++實現如下:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>v={{}};
for(int i=0;i<nums.size();++i)
{
int n=v.size();
for(int j=0;j<n;++j)
{
v.emplace_back(v[j]);
v[n+j].emplace_back(nums[i]);
}
}
return v;
}
};
回溯:
以示例爲例,畫出樹狀圖。每次添加一個元素,每次只添加上一節點右邊的元素。每一個節點即爲一個子集。
c++實現如下:
class Solution {
public:
void dfs(vector<int>&nums,vector<vector<int>>& v,vector<int>& tmp,int start)
{
for(int i=start;i<nums.size();++i)
{
tmp.emplace_back(nums[i]);//向當前子集添加元素
v.emplace_back(tmp);//將新子集放入子集數組
dfs(nums,v,tmp,i+1);
tmp.pop_back();//上一節點下所有節點遍歷完成,將元素取出以便更換
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>v={{}};//存放所有子集
vector<int>tmp;//存放一個子集
dfs(nums,v,tmp,0);
return v;
}
};
題目來源:leetcode