函數代碼中調用自己時稱為遞歸,該函數被稱為遞歸函數。遞歸函數是一個很高效的 開發技巧,可以極大的簡化代碼提高開發效率。遞歸函數與循環類似,循環可以完成的 事情,遞歸函數都可以完成,并且對于一些復雜的問題,遞歸函數的實現代碼更簡單
#include<stdio.h>
int compute_sum(int i){
if(i > 3){
return0;
}
return i + compute_sum(i+1);
}
int main(){
printf("sum = %d\n,compute_sum(1)");
return 0;
}
}
遞歸函數的一般形式
void func(){
if(){
...
return;
}//子集調用自己
func;
}
練習,鏈表遞歸實現
#include<stdio.h>
#include<vector>
struct ListNode{//鏈表數據結構
int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){}
};
void add_to_vector(ListNode *head,std::vector<int> & vec){
if(!head){//節點為空
return;
}
vec.push_back(head->val);
add_to_vector(head->next,vec);
}
int main(){
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
std::vector<int> vec;
// 遞歸將head指針指向的節點數據val,push到vec里
add_to_vector(&a,vec);
for(int i = 0;i< vec.size();i++){
printf("[%d]",vec[i]);
}
printf("\n");
return 0;
}
回溯法
回溯法又稱為試探法,但當探索到某一步時,發現原先選擇達不到 目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法。
預備知識-循環
#include<stdio.h>
#include<vector>
int main(){
std::vector<int> nums;
nums.push_back(1);
nums.push_back(2);
nums.push_back(3);
std::vector<int> item;//item 生成各個子集的數組
std::vector<std:: vector<int> > result;//最終結果數組
for(int i= 0; i < nums.size(); i++){
item.push_back(nums[i]);
result.push_back(item);
}
for(int i =0; i < result.size();i++){
for(int j = 0; j < result[i].size();j++){
printf("[%d]",result[i][j])
}
printf("\n");
}
return 0;
}
預備知識-遞歸
#include<stdio.h>
#include<vector>
//nums[] =[1,2,3], 將子集[1],[1,2],[1,2,3]遞歸的加入result;
void generate(int i , std::vector<int> &nums,std::vector<int> &item, std::vector<std::vector<int>> &result){
if(i >= nums.size()){
return;
}
item.push_back(nums[i]);
result.push_back(item);
generate(i+1,nums,item,result);
)
}
求子集
已知一組數(其中無重復元素),求這組數可以組成的所有子集。 結果中不可有無重復的子集。
例如: nums[] = [1, 2, 3]
結果為: [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
LeetCode 78. Subsets
算法思路
利用回溯方法生成子集,即對于每個元素,都有試探放入或不放入集合中的 兩個選擇:
選擇放入該元素,遞歸的進行后續元素的選擇,完成放入該元素后續所有元素 的試探;之后將其拿出,即再進行一次選擇不放入該元素,遞歸的進行后續元素的選擇,完成不放入該元素后續所有元素的試探。 本來選擇放入,再選擇一次不放入的這個過程,稱為回溯試探法。
例如:
元素數組: nums = [1, 2, 3, 4, 5,...] ,子集生成數組item[] = []
對于元素1,
選擇放入item,item = [1],繼續遞歸處理后續[2,3,4,5,...]元素;item = [1,...]
選擇不放入item,item = [],繼續遞歸處理后續[2,3,4,5,...]元素;item = [...]
算法設計
代碼實現
#include<vector>
class Solution{
public:
std::vector<std::vector<int>> subsets(std::vector<int> &nums){
std::vector<std::vector<int>> result;//存儲最終結果的result;
std::vector<int> item ;//回溯時產生各個子集的數組
result.push_back(item);//將空集push進入result
generate(0,nums,item,result);//計算各個子集
return result;
private:
void generate(int i , std::vector<int> &nums,std::vector<int> &item,std::vector<std::vector<int>> &result){
if(i > = nums.size()){
return;
}
item.push_back(nums[i]);
result.push_back(item);//將生成的子集添加進入result
generate(i+1,nums,item,result);//第一次遞歸調用
item.pop_back();
generate(i+1,nums,item,result);//第二次遞歸調用
}
};
方法2 位運算
若一個集合有三個元素A,B,C,則3個元素有23= 8種,用0-7表示這些集合
A元素為100 = 4,B元素為010 = 2;C元素為001=1;圖構造某一集合,及使用A,B,C對應的三個整數與該集合對應的整數做&運算,當為真時,將該元素push進集合
#include<vector>
class Solution{
public:
std::vector<std:: vector<int>> subsets(std::vector<int> &nums){
std:;vector<std::vector<int>> result;
int all_set = 1<<nums.size() //2^n
for(int i = 0;i<all_set;i++){遍歷所有集合
std:vector<int> item;
for(int j = 0; j< nums.size();j++){
if(i & (1 << j )){
item.push_back();
}//整數i 代表從 0 至 2^n-1^,這2^n^個集合
}//(1 << j 構造nums數組的第j個元素
result.push_back(item)
}
return result;
}
}
求子集2
已知一組數組(其中有重復元素),求這組數可以組成的所有子集。結果中無重復的子集;
例如: nums[] = [2, 1, 2, 2]
結果為: [[], [1], [1,2], [1,2,2], [1,2,2,2], [2], [2,2], [2,2,2]]
注意: [2,1,2]與[1,2,2]是重復的集合!
LeetCode 90. Subsets II
有兩種重復原因:
1.不同位置的元素組成的集合是同一個子集,順序相同: 例如: [2, 1, 2, 2] ,
選擇第1,2,3個元素組成的子集:[2,1,2]; 選擇第1,2,4個元素組成的子集:[2,1,2]。
2.不同位置的元素組成的集合是同一個子集,雖然順序不同,但仍然
代表了同一個子集,因為集合中的元素是無序的。 例如: [2, 1, 2, 2] ,
選擇第1,2,3個元素組成的子集:[2, 1, 2]; 選擇第2,3,4個元素組成的子集:[1, 2, 2]。
算法設計
不同位置的元素組成的集合是同一個子集,子集的各個元素順序相同 ,或順序不同,解決辦法
例如: [2, 1, 2, 2] :
選擇第1,2,3個元素組成的子集:[2, 1, 2];
選擇第1,2,4個元素組成的子集:[2, 1, 2];
選擇第2,3,4個元素組成的子集:[1, 2, 2]。
解決方案: 先對原始nums數組進行排序,再使用set去重!
例如: [2, 1, 2, 2]排序后: [1, 2, 2, 2] 無論如何選擇,均只出現[1, 2, 2]
#include<vector>
#include<set>
#include<algorithm>
class Solution{
public:
std::vector<std::vector<int>> subsetsWithDup(std :: vector<int> &nums){
std::vector<std::vector<int>> result;
std::vector<int> item;
std::set<std::vector<int>> res_set;
std::sort(nums.begin(),nums.end());
result.push_back(item);
generate(0,nums,result,item,res_set);
return result;
}
private:
void generate(int i ,std::vector<int> &nums,std::vevtor<std::vector<int>> &result,std::vector<int> &item,std::set<std::vector<int>> &res_set){
if(i > nums.size()){
return;
}
item.push_back(nums[i]);
if(res_set.find(item) == res_set.end()){
result.push_back(item);
res_set.insert(item);
}
generate(i+1,nums,result,item,res_set);
item.pop_back();
generate(i+1,nums,result,item,res_set);
}
};
組合數之和2
已知一數組(其中有重復元素),求這組數可以組成的所有子集,子集中的各個元素和整數target的子集,結果中無重復的子集。
LeetCode 40. Combination Sum II
無論是回溯法或位運算法,整體時間復雜度O(2^n)。 例如:
nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8;
[10] > target,則所有包含[10]的子集[10,...],一定不滿足目標。
[1, 2, 7] > target,則所有包含[1, 2, 7]的子集[1, 2, 7,...],一定不滿足目標。 [7, 6] > target,則所有包含[7, 6]的子集[7, 6,...],一定不滿足目標。
算法設計
在搜索回溯過程中進行剪枝操作: 遞歸調用時,計算已選擇元素的和
sum,若sum >target,不再進行更 深的搜索,直接返回。
例如:
nums[] = [10, 1, 2, 7, 6, 1, 5] target = 8
...
過多的錯誤嘗試,浪費了大量時間。
#include<vector>
#include<set>
#include<algorithm>
class Solution{
piblic:
std::vector<std::vetor<int>> combinationSum2(std::vector<int> & candidates, int target){
std::vector<std::vector<int>> result;
std::vector<int> item;
std::vector<std::vector<int>> res_item;
std::sort(candidates.begin(),candidates.end());
generate(0,candidates,result,item,res_set,0,target);
return;
private:
void generate(int i , std::vector<int> &nums,std::vector<std::vector<int>> &result, std::vector<std::vector<int>>&item,std::set<std::vector<int>> &res_set,int sum, int target){
if(i > nums.size() || sum > target) {
return;
}
sum +=nums[i];
item.push_back(noms[i]);
if(sum == target && res_set.find(item) == res_set.end()){
result.push_back(item);
res_set.push_back(item);
}
generate(i+1,nums,result,item,res_set,sum,target);
sum - = nums[i];
item.pop_back();
generate(i+1,nums,result,item,res_set,sum,target);
}
}
};