当前位置:   article > 正文

回溯【基础算法精讲 14】_dfs为什么要恢复现场

dfs为什么要恢复现场

视频地址 : 

回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili

基本概念

1 . 例子

例如从abc和def(n = 2)中各选出一个组成新的字符串?

 如果n很大 , 这个时候for循环的表达能力有限 ;

2 . 原问题 和 子问题

 3 . 增量构造答案

这个增量构造答案的过程就是回溯的特点 ;

回溯三问

关于恢复现场的理解

在递归到某一“叶子节点”(非最后一个叶子)时,答案需要向上返回,而此时还有其他的子树(与前述节点不在同一子树)未被递归到,又由于path为全局变量。若直接返回,会将本不属于该子树的答案带上去,故需要恢复现场。
恢复现场的方式为:在递归完成后 dfs(i+1); 后,进行与“当前操作”相反的操作,“反当前操作”。 

17 . 电话号码的字母组合

这也就是n(n=8)个字符串中选两个的组合问题 ;

思路 : 

1 . 先创建一个下标 与 对应字符串映射的数组,这里使用hash表进行映射也是可以的 ;

2 . 对于回溯 , 如果到了叶子节点 , 那么就直接添加即可 , 对于每一个path[i],都是存放digits[i]中数字字符对应字符串中的一个字符 , 遍历该字符串 , 对每一个字符进行递归操作 ;

3 . 对于不用恢复现场 : 因为每次递归到 i,一定会修改 path【i】,那么递归到终点时,每个 path【i】 都被覆盖成要枚举的字母,所以不需要恢复现场。

代码实现 : 

  1. class Solution {
  2. public:
  3. const string mp[10] = {
  4. "", //0
  5. "", //1
  6. "abc", //2
  7. "def", //3
  8. "ghi", //4
  9. "jkl", //5
  10. "mno", //6
  11. "pqrs", //7
  12. "tuv", //8
  13. "wxyz", //9
  14. };
  15. vector<char> path ;
  16. vector<string> ans ;
  17. int n ;
  18. string get(){
  19. string tmp = "" ;
  20. for(char c : path){
  21. tmp += c ;
  22. }
  23. return tmp ;
  24. }
  25. void dfs(string digits , int i){
  26. // 先写递归终止条件
  27. if(i == n){
  28. ans.push_back(get()) ;
  29. return ; // 回溯
  30. }
  31. for(char c : mp[digits[i]-'0']){
  32. path[i] = c ;
  33. dfs(digits , i + 1) ;
  34. // 因为每一次path[i]都会被覆盖 , 那么就不用进行回溯
  35. }
  36. }
  37. vector<string> letterCombinations(string digits) {
  38. n = digits.size() ;
  39. if(n == 0) return ans ;
  40. path.resize(n) ;
  41. dfs(digits , 0) ;
  42. return ans ;
  43. }
  44. };

更加优雅的写法 : 

  1. class Solution {
  2. string MAPPING[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
  3. public:
  4. vector<string> letterCombinations(string digits) {
  5. int n = digits.length();
  6. if (n == 0) return {};
  7. vector<string> ans;
  8. string path(n, 0); // 本题 path 长度固定为 n
  9. function<void(int)> dfs = [&](int i) {
  10. if (i == n) {
  11. ans.emplace_back(path);
  12. return;
  13. }
  14. for (char c : MAPPING[digits[i] - '0']) {
  15. path[i] = c; // 直接覆盖
  16. dfs(i + 1);
  17. }
  18. };
  19. dfs(0);
  20. return ans;
  21. }
  22. };

78 . 子集

链接 : 

. - 力扣(LeetCode)

法一(对于每一个数选或不选) : 

  1. class Solution {
  2. public:
  3. vector<vector<int>> ans ;
  4. vector<int> path ;
  5. void dfs(vector<int>& nums , int i){
  6. if(i==nums.size()){
  7. ans.push_back(path) ;
  8. return ;// 回溯
  9. }
  10. // 不选
  11. dfs(nums,i+1) ;// 不选i
  12. //
  13. path.push_back(nums[i]) ;
  14. dfs(nums,i+1) ;
  15. path.pop_back() ;
  16. }
  17. vector<vector<int>> subsets(vector<int>& nums) {
  18. if(nums.size()==0) return ans ;
  19. ans.clear() ;
  20. path.clear() ;
  21. dfs(nums,0) ;
  22. return ans ;
  23. }
  24. };

法二(枚举第i个数选谁):

 这样的话  每一个结点都是答案 ;

  1. class Solution {
  2. public:
  3. vector<vector<int>> ans;
  4. vector<int> path;
  5. void backtrack(vector<int>& nums,int idx){
  6. ans.push_back(path);
  7. if(idx >= nums.size()) return;
  8. for(int i=idx;i<nums.size();i++){
  9. path.push_back(nums[i]);
  10. backtrack(nums,i+1);
  11. path.pop_back();
  12. }
  13. }
  14. vector<vector<int>> subsets(vector<int>& nums) {
  15. path.clear();
  16. ans.clear();
  17. backtrack(nums,0);
  18. return ans;
  19. }
  20. };

131 . 分割回文串

链接 : 

. - 力扣(LeetCode)

思路 : 

 代码 (枚举子串结束位置): 

  1. class Solution {
  2. public:
  3. bool pd(string s , int l ,int r){
  4. while(l <= r){
  5. if(s[l++] != s[r--])
  6. return false;
  7. }
  8. return true ;
  9. }
  10. vector<vector<string>> ans ;
  11. vector<string> path ;
  12. int n = 0 ;
  13. // 答案视角 , 判断每一个子串结束的地方
  14. void dfs(string s , int i){
  15. if(i==n){
  16. ans.push_back(path) ;
  17. return ;
  18. }
  19. for(int j=i;j<n;j++){ // 枚举子串结束的位置
  20. if(pd(s,i,j)){
  21. path.push_back(s.substr(i,j-i+1)) ;
  22. dfs(s,j+1) ;
  23. path.pop_back();// 恢复现场
  24. }
  25. }
  26. }
  27. vector<vector<string>> partition(string s) {
  28. n = s.size() ;
  29. dfs(s,0) ;
  30. return ans ;
  31. }
  32. };

代码(每个分割点选不选) : 

  1. class Solution {
  2. public:
  3. bool pd(string s , int l ,int r){
  4. while(l <= r){
  5. if(s[l++] != s[r--])
  6. return false;
  7. }
  8. return true ;
  9. }
  10. vector<vector<string>> partition(string s) {
  11. vector<vector<string>> ans ;
  12. vector<string> path ;
  13. // 判断每个逗号选或不选
  14. int n = s.size() ;
  15. // start 表示这一段回文串的开始位置
  16. function<void(int,int)> dfs = [&](int i,int start){
  17. if(i==n){
  18. ans.push_back(path) ;
  19. return ;
  20. }
  21. // 不选i-i+1
  22. if(i < n - 1){
  23. dfs(i+ 1, start) ;
  24. }
  25. //
  26. if(pd(s,start,i)){
  27. path.push_back(s.substr(start,i-start+1));
  28. dfs(i + 1 , i + 1 ) ;
  29. path.pop_back() ;
  30. }
  31. };
  32. dfs(0 , 0) ;
  33. return ans ;
  34. }
  35. };

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/916218
推荐阅读
相关标签
  

闽ICP备14008679号