文章目录
- 引言
- 一、全排列
- 1. 决策树
- 2. 设计代码
- 1. 全局变量
- 2. dfs函数
- 3. 细节问题
- 二、子集
- 解法一
- 1. 决策树
- 2. 设计代码
- 1. 全局变量
- 2. dfs函数
- 3. 细节问题
- 解法二
- 1. 决策树
- 2. 设计代码
- 1. 全局变量
- 2. dfs函数
- 3. 细节问题
- 三、子集的异或总和之和
- 四、全排列 ||
- 五、电话号码的字母组合
- 六、括号生成
- 七、组合
- 八、目标和
- 九、组合总和
- 十、字母大小写全排列
- 十一、优美的排列
- 总结
引言
在实际的dfs问题中,大多时候并不会直接告诉你,而是需要自己发现可以使用dfs来解决。而是否能用dfs解决的关键,就是画出决策树!同时,不同的决策树代表不同的解决方式,对于同一问题,好的决策树往往能节省时间,提高效率。
一、全排列
1. 决策树
绿色部分,就是剪枝,因为全排列不能重复枚举。
2. 设计代码
1. 全局变量
- vector<vector< int >> ret:用来保存最终所有的结果
- vector< int > path:用来保存单一路径的结果
- bool check[6]:用来实现剪枝
2. dfs函数
- 将数组中所有数枚举一遍,如果没有枚举过,则将其加入path
3. 细节问题
- 回溯
- 清除path中最后一个数
- 更改check中的标记
- 剪枝:根据check的标记,去除重复枚举的情况
- 递归出口:当path路径长度等于枚举数组长度,则将其加入ret,返回
class Solution
{vector<vector<int>> ret;vector<int> path;bool check[6];//实现剪枝
public:void dfs(vector<int>& nums){if(path.size() == nums.size()){ret.push_back(path);return;}for(int i=0; i<nums.size(); ++i){if(!check[i])//剪枝{path.push_back(nums[i]);check[i] = true;dfs(nums);//回溯 - 恢复现场path.pop_back();check[i] = false;}}}vector<vector<int>> permute(vector<int>& nums){dfs(nums);return ret;}
};
二、子集
解法一
1. 决策树
2. 设计代码
1. 全局变量
- vector<vector< int >> ret:用来保存最终所有的结果
- vector< int > path:用来保存单一路径的结果
2. dfs函数
- 对于数组中的每一个数,都遵循选或不选两种方式
- 不选:直接dfs下一层
- 选:先将其加入path,再dfs下一层
- dfs(nums, i):增加参数i作为当前数的下标
3. 细节问题
- 回溯:删除path中最后一个数
- 递归出口:当i枚举完所有数,来到数组末尾,则将path加入ret,返回
class Solution
{vector<vector<int>> ret;vector<int> path;
public:void dfs(vector<int>& nums, int i){if(i == nums.size()){ret.push_back(path);return;}//不选dfs(nums, i+1);//选path.push_back(nums[i]);dfs(nums, i+1);path.pop_back();}vector<vector<int>> subsets(vector<int>& nums){dfs(nums, 0);return ret;}
};
解法二
1. 决策树
由于决策树不同的选取,解法二要优于解法一
2. 设计代码
1. 全局变量
- vector<vector< int >> ret:用来保存最终所有的结果
- vector< int > path:用来保存单一路径的结果
2. dfs函数
- 按照集合中元素的个数进行分类
- 依据集合的互异性,每层dfs只能选取下标i后面的数
- dfs(nums, i):增加参数i作为当前数的下标
3. 细节问题
- 回溯:删除path中最后一个数
- 递归出口:每一层path都是结果,都需要添加到ret
class Solution
{vector<vector<int>> ret;vector<int> path;
public:void dfs(vector<int>& nums, int i){ret.push_back(path);for(int j=i; j<nums.size(); ++j){path.push_back(nums[j]);dfs(nums, j+1);path.pop_back();}}vector<vector<int>> subsets(vector<int>& nums){dfs(nums, 0);return ret;}
};
三、子集的异或总和之和
思路:子集的变式题(按照集合元素个数分类)
class Solution
{int ret = 0;int path = 0;
public:void dfs(vector<int>& nums, int pos){ret += path;for(int i=pos; i<nums.size(); ++i){path ^= nums[i];dfs(nums, i + 1);path ^= nums[i];}}int subsetXORSum(vector<int>& nums){dfs(nums, 0);return ret;}
};
四、全排列 ||
思路:本题是全排列的进阶版,存在重复元素,所以剪枝是关键。
- 前提:先对数组排序
- 同一个元素只能使用一次(check)
- 对于每一个节点,相同的元素只能选一次
class Solution
{vector<vector<int>> ret;vector<int> path;bool check[8];
public:void dfs(vector<int>& nums){if(path.size() == nums.size()){ret.push_back(path);return;}for(int i=0; i<nums.size(); ++i){if(!check[i] && (i == 0 || nums[i] != nums[i-1] || check[i-1])){path.push_back(nums[i]);check[i] = true;dfs(nums);check[i] = false;path.pop_back();}}}vector<vector<int>> permuteUnique(vector<int>& nums){sort(nums.begin(), nums.end());dfs(nums);return ret;}
};
五、电话号码的字母组合
细节:用哈希表存储数字与字符串的映射关系
class Solution
{vector<string> ret;string path;vector<string> hash = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:void dfs(string& digits, int pos){if(path.size() == digits.size()){ret.push_back(path);return;}int n = digits[pos] - '0';string s = hash[n];for(int i=0; i<s.size(); ++i){path.push_back(s[i]);dfs(digits, pos + 1);path.pop_back();}}vector<string> letterCombinations(string& digits){if(digits.size() == 0) return ret;dfs(digits, 0);return ret;}
};
六、括号生成
class Solution
{vector<string> ret;string path;int n;
public:void dfs(int left, int right){if(right == n){ret.push_back(path);return;}if(left < n){path.push_back('(');dfs(left + 1, right);path.pop_back();}if(right < left){path.push_back(')');dfs(left, right + 1);path.pop_back();}}vector<string> generateParenthesis(int _n){n = _n;dfs(0, 0);return ret;}
};
七、组合
class Solution
{vector<vector<int>> ret;vector<int> path;int n, k;
public:void dfs(int pos){if(path.size() == k){ret.push_back(path);return;}for(int i=pos; i<=n; ++i){path.push_back(i);dfs(i + 1);path.pop_back();}}vector<vector<int>> combine(int _n, int _k){n = _n, k = _k;dfs(1);return ret;}
};
八、目标和
class Solution
{int ret = 0;int t;
public:void dfs(vector<int>& nums, int pos, int path){if(pos == nums.size()){if(path == t) ++ret;return;}dfs(nums, pos + 1, path + nums[pos]);dfs(nums, pos + 1, path - nums[pos]);}int findTargetSumWays(vector<int>& nums, int target){t = target;dfs(nums, 0, 0);return ret;}
};
九、组合总和
class Solution
{vector<vector<int>> ret;vector<int> path;int t;
public:void dfs(vector<int>& candidates, int pos, int sum){if(sum >= t){if(sum == t) ret.push_back(path);return;}for(int i=pos; i<candidates.size(); ++i){path.push_back(candidates[i]);dfs(candidates, i, sum + candidates[i]);path.pop_back();}}vector<vector<int>> combinationSum(vector<int>& candidates, int target){t = target;dfs(candidates, 0, 0);return ret;}
};
十、字母大小写全排列
class Solution
{vector<string> ret;
public:void dfs(string& s, int pos, string path){if(path.size() == s.size()){ret.push_back(path);return;}if(isalpha(s[pos])){dfs(s, pos + 1, path + (char)tolower(s[pos]));dfs(s, pos + 1, path + (char)toupper(s[pos]));}else dfs(s, pos + 1, path + s[pos]);}vector<string> letterCasePermutation(string& s){dfs(s, 0, "");return ret;}
};
十一、优美的排列
class Solution
{int ret = 0;bool check[20];int n;
public:void dfs(int pos, int path){if(path == n){++ret;return;}for(int i=1; i<=n; ++i){if(!check[i] && (i % pos == 0 || pos % i == 0)){check[i] = true;dfs(pos + 1, path + 1);check[i] = false;}}}int countArrangement(int _n){n = _n;dfs(1, 0);return ret;}
};
总结
- 决策树
- 设计代码
- 全局变量
- dfs函数
- 细节问题
- 回溯
- 剪枝
- 递归出口