递归、回溯、DFS的区别
递归是一种算法结构,DFS是一种搜索(方法)工具,回溯是一种算法思想
在函数中调用函数本身来解决子问题以达到解决原问题的方法就叫递归
对于一个可以分解的问题,子问题与原问题处理过程完全相同,区别只在于数据规模,可以用递归来解
回溯就是通过不同的尝试来搜索问题的解
有点类似于穷举(搜索全部解空间),但是和穷举不同的是回溯会“剪枝”,对已经知道错误的结果没必要再枚举接下来的答案了
回溯搜索是深度优先搜索(DFS)的一种情况
对于某一个搜索树来说(搜索树是起记录路径和状态判断的作用),回溯和DFS,其主要的区别是,回溯法在求解过程中不保留完整的树结构(剪枝),而深度优先搜索则记下完整的搜索树
在深度优先搜索中,用标志的方法记录访问过的状态,这种处理方法使得深度优先搜索法与回溯法没什么区别了
回溯模板
void backtracking(参数) { if (终止条件) { 收集结果; return; } for (该节点的所有子节点){ 处理结点; 递归函数; 回溯操作(比如 收集结果12 撤销2,收集结果13 撤销3); } }
|
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
示例 1:
输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。
|
示例 2:
输入: candidates = , target = 8 输出:
|
示例 3:
输入: candidates = , target = 1 输出:
|
提示:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
candidates
的所有元素 互不相同
1 <= target <= 40
C++
class Solution { private: vector<vector<int>> result; vector<int> path; void backtracking(vector<int>& candidates, int target, int sum, int startIndex) { if (sum > target) return; if (sum == target) { result.push_back(path); return; }
for (int i = startIndex; i < candidates.size(); i++) { sum += candidates[i]; path.push_back(candidates[i]); backtracking(candidates, target, sum, i); sum -= candidates[i]; path.pop_back(); } } public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { backtracking(candidates, target, 0, 0); return result; } };
|
上面的版本一的代码可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。
其实如果已经知道下一层的sum会大于target,就没有必要进入下一层递归了。
那么可以在for循环的搜索范围上做做文章了。
对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历。
class Solution { private: vector<vector<int>> result; vector<int> path; void backtracking(vector<int>& candidates, int target, int sum, int startIndex) { if (sum == target) { result.push_back(path); return; }
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) { sum += candidates[i]; path.push_back(candidates[i]); backtracking(candidates, target, sum, i); sum -= candidates[i]; path.pop_back(); } } public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { sort(candidates.begin(), candidates.end()); backtracking(candidates, target, 0, 0); return result; } };
|
class Solution { public: vector<vector<int>> res; vector<int> path; vector<vector<int>> combinationSum(vector<int>& candidates, int target) { dfs(0, candidates, target, path); return res; }
void dfs(int idx,vector<int>& candidates, int target, vector<int> path){ if(idx == candidates.size() || target < 0) return; if(target == 0) { res.push_back(path); return; }
dfs(idx + 1, candidates, target, path);
path.push_back(candidates[idx]); dfs(idx, candidates, target - candidates[idx], path); } };
|
class Solution { public: vector<vector<int>> res; set<vector<int>> st; vector<int> path; vector<vector<int>> combinationSum(vector<int>& candidates, int target) { dfs(0, candidates, target, path); for(auto elem : st){ res.push_back(elem); } return res; }
void dfs(int cur, vector<int>& candidates, int target, vector<int> path){ if(target < 0) return; if(target == 0) st.insert(path); target = target - cur; path.push_back(cur); for(auto elem : candidates){ dfs(elem, candidates, target, path); } } };
|
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = , target = 8, 输出:
|
示例 2:
输入: candidates = , target = 5, 输出:
|
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
C++
避免重复思想: 这个方法最重要的作用是,可以让同一层级,不出现相同的元素。即 1 / \ 2 2 这种情况不会发生 但是却允许了不同层级之间的重复即: / \ 5 5 例2 1 / 2 这种情况确是允许的 / 2
|
class Solution { public: vector<vector<int>> res; vector<int> path; vector<vector<int>> combinationSum2(vector<int>& candidates, int target){ sort(candidates.begin(), candidates.end()); backtracking(candidates, target, 0, 0); return res; }
void backtracking(vector<int>& candidates, int target, int sum, int startIndex){ if(sum == target){ res.push_back(path); return; }
for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i ++){ if(i > startIndex && candidates[i] == candidates[i -1]) continue; sum += candidates[i]; path.push_back(candidates[i]); backtracking(candidates, target, sum, i + 1); sum -= candidates[i]; path.pop_back(); } } };
|
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
示例 2:
提示:
C++
class Solution { public: vector<vector<int>> res; vector<int> path; vector<vector<int>> combine(int n, int k) { backtracking(n, k, 1); return res; }
void backtracking(int n, int k, int startIdx){ if(path.size() == k){ res.push_back(path); return; }
for(int i = startIdx; i <= n; i ++){ path.push_back(i); backtracking(n, k, i + 1); path.pop_back(); } } };
|
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
示例 2:
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
中的所有元素 互不相同
C++
class Solution { public: vector<vector<int>> res; vector<int> path = {}; vector<vector<int>> subsets(vector<int>& nums) { for(int i = 0; i <= nums.size(); i ++){ backtracking(nums, 0, i); } return res; }
void backtracking(vector<int>& nums, int startIdx, int cnt){ if(path.size() == cnt){ res.push_back(path); return; }
for(int i = startIdx; i < nums.size(); i ++){ path.push_back(nums[i]); backtracking(nums, i + 1, cnt); path.pop_back(); } } };
|
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
示例 2:
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
C++
class Solution { public: vector<vector<int>> res; vector<int> path = {}; vector<vector<int>> subsetsWithDup(vector<int>& nums) { sort(nums.begin(), nums.end()); for(int i = 0; i <= nums.size(); i ++){ backtracking(nums, 0, i); } return res; }
void backtracking(vector<int>& nums, int startIdx, int cnt){ if(path.size() == cnt){ res.push_back(path); return; }
for(int i = startIdx; i < nums.size(); i ++){ if(i > startIdx && nums[i] == nums[i - 1]) continue; path.push_back(nums[i]); backtracking(nums, i + 1, cnt); path.pop_back(); } } };
|
给定一个字符串 s
,通过将字符串 s
中的每个字母转变大小写,我们可以获得一个新的字符串。
返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。
示例 1:
输入:s = "a1b2" 输出:["a1b2", "a1B2", "A1b2", "A1B2"]
|
示例 2:
输入: s = "3z4" 输出: ["3z4","3Z4"]
|
提示:
1 <= s.length <= 12
s
由小写英文字母、大写英文字母和数字组成
C++
class Solution { public: vector<string> res; vector<string> letterCasePermutation(string s) { backtracking(s,0); return res; } void backtracking(string s, int idx){ res.push_back(s); for(int i = idx; i < s.size(); i++){ if('0'<=s[i]&&s[i]<='9') continue; else{ s[i] = s[i] ^ 32; backtracking(s,i+1); s[i] = s[i] ^ 32; } } } };
|