首页 > 其他分享 >回溯法

回溯法

时间:2023-07-01 11:36:11浏览次数:34  
标签:nums int backtrace vector result 回溯 path


回溯法分类(backtack)

  • 组合问题
  • 分割问题
  • 子集问题
  • 排序问题
  • 棋盘问题
  • 其他

参考链接

关于回溯算法,你该了解这些!

组合问题

77. 组合

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    vector<vector<int>> combine(int n, int k) {
        backtrace(n, k, 1);
        return result;
    }

    void backtrace(int n, int k, int startIndex) {
        if (path.size() == k) {
            result.emplace_back(path);
            return;
        }

        for (int i = startIndex; i <= n - (k - path.size()) + 1; ++i) { // 控制树的横向遍历
            path.emplace_back(i);
            backtrace(n, k, i + 1);
            path.pop_back();
        }
    }
};

216. 组合总和 III

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    int target;
    const int nums = 9;
    vector<vector<int>> combinationSum3(int k, int n) {
        target = n;
        backtrace(k, 1);
        return result;
    }

    void backtrace(int k, int startIndex) {
        if (path.size() == k) {
            if (accumulate(path.begin(), path.end(), 0) == target) {
                result.emplace_back(path);
            }
            return;
        }
        for (int i = startIndex; i <= nums - (k - path.size()) + 1; ++i) { // 控制树的横向遍历
            path.emplace_back(i);
            backtrace(k, i + 1);
            path.pop_back();
        }
    }
};

17. 电话号码的字母组合

class Solution {
public:
    vector<string> result; // 存放符合条件结果的集合
    string path;   // 用来存放符合条件的结果
    map<char, string> numStr = {
        {'2', "abc"}, {'3', "def"}, {'4', "ghi"}, {'5', "jkl"}, {'6', "mno"}, {'7', "pqrs"}, {'8', "tuv"}, {'9', "wxyz"}
    };
    string s;
    vector<string> letterCombinations(string digits) {
        if (!digits.size()) return result;
        backtrace(digits, 0);
        return result;
    }

    void backtrace(string &digits, int idx) {
        if (idx == digits.size()) {
            result.emplace_back(path);
            return;
        }
        for (int i = 0; i < numStr[digits[idx]].size(); ++i) { // 控制树的横向遍历
            path.push_back(numStr[digits[idx]][i]);
            backtrace(digits, idx + 1); // 问题所在,是idx而非i
            path.pop_back();
        }
    }
};

39. 组合总和

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    int curr = 0;           // 记录path当前和
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtrace(candidates, target, 0);
        return result;        
    }

    void backtrace(vector<int>& candidates, int target, int idx) {
        if (curr > target) {
            return;
        }
        if (curr == target) {
            result.emplace_back(path);
            return;
        }

        for (int i = idx; i < candidates.size(); ++i) { // 控制树的横向遍历
            if (curr + candidates[i] <= target) {
                path.emplace_back(candidates[i]);
                curr += candidates[i];
                backtrace(candidates, target, i); // // 不用i+1了,表示可以重复读取当前的数  ===== 理解这个地方放idx和i的区别
                curr -= candidates[i];
                path.pop_back();
            }
        }
    }
};

40. 组合总和 II

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    int curr = 0;           // 记录path当前和
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());    // 需要注意的是去重需要排序
        backtrace(candidates, target, 0);
        return result;        
    }

    void backtrace(vector<int>& candidates, int target, int idx) {
        if (curr == target) {
            result.emplace_back(path);
            return;
        }

        for (int i = idx; i < candidates.size() && curr + candidates[i] <= target; ++i) { // 控制树的横向遍历
            if (i > idx && candidates[i] == candidates[i - 1]) {  // 如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
                continue;
            }
            path.emplace_back(candidates[i]);
            curr += candidates[i];
            backtrace(candidates, target, i + 1);  // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
            curr -= candidates[i];
            path.pop_back();
        }
    }
};

分割问题

切割问题其实是一种组合问题!

131. 分割回文串

class Solution {
public:
    vector<vector<string>> result; // 存放符合条件结果的集合
    vector<string> path;   // 用来存放符合条件的结果
    int n;
    vector<vector<string>> partition(string s) {
        n = s.size();
        vector<vector<bool>> judge(n, vector<bool>(n, false)); // 判断是否为回文子串
        fullPalindrome(s, judge);  // 填充回文子串
        backtrace(s, 0, judge);
        return result;        
    }

    void backtrace(string &s, int idx, vector<vector<bool>> judge) {
        if (idx == n) {
            result.push_back(path);
            return;
        }

        for (int i = idx; i < n; ++i) { // 控制树的横向遍历
            if (judge[idx][i]) {  // 判断是否为回文子串
                // 获取[idx,i]在s中的子串
                string str = s.substr(idx, i - idx + 1);
                path.emplace_back(str);
            } else {    // 如果不是直接跳过
                continue;
            }
            backtrace(s, i + 1, judge);  // 寻找i+1为起始位置的子串
            path.pop_back();
        }
    }

    void fullPalindrome(string &s, vector<vector<bool>> &judge) {
        // 初始化
        for (int i = s.size() - 1; i >= 0; i--) { // 借鉴别人写的,自己还不是很熟
            // 需要倒序计算, 保证在i行时, i+1行已经计算好了
            for (int j = i; j < s.size(); j++) {
                if (j == i) {judge[i][j] = true;}
                else if (j - i == 1) {judge[i][j] = (s[i] == s[j]);}
                else {judge[i][j] = (s[i] == s[j] && judge[i+1][j-1]);}
            }
        }
    }
};

93. 复原 IP 地址

class Solution {
public:
    vector<string> result; // 存放符合条件结果的集合
    const int pointCnt = 3;
    vector<string> restoreIpAddresses(string s) {
        backtrace(s, 0, 0);
        return result;        
    }

    void backtrace(string &s, int idx, int num) {
        if (num == pointCnt) {
            if (isValid(s, idx, s.size() - 1)) {
                // 判断最后一段数字是否有效
                result.push_back(s);
            }
            return;
        }

        for (int i = idx; i < s.size(); ++i) { // 控制树的横向遍历
            if (i - idx < 3 && isValid(s, idx, i)) {  // 判断是否为有效分割
                // 获取[idx,i]在s中的子串
                s.insert(s.begin() + i + 1, '.');    // 在i后面插一个.号
                num++;
                backtrace(s, i + 2, num);            // 插入逗号后之后的下一个子串的起始位置为i+2
                num--;
                s.erase(s.begin() + i + 1);         // 回溯删除逗号
            } else {    // 如果不是直接跳过
                break;      // 以下更不符合
            }
        }
    }

    bool isValid(const string &s, int start, int end) {  // 判断是否分割有效
        // 判断条件:
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) {  // 前导0,原来这么简单判断即可
            return false;
        }
        int num = 0;    // 统计数字的大小
        for (int i = start; i <= end; ++i) {
            int temp = s[i] - '0'; // 将字符转换为数字
            num = num * 10 + temp;  // 注意此处不能缩写*=,会出错
            if (num > 255) {    // 判断范围在0到255之间
                return false;
            }
        }
        return true;
    }
};

子集问题

78. 子集

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    int n;
    vector<vector<int>> subsets(vector<int>& nums) {
        n = nums.size();
        backtrace(nums, 0);
        return result;
    }

    void backtrace(vector<int>& nums, int idx) {
        result.emplace_back(path);
        if (path.size() == n) {
            return;
        }
        for (int i = idx; i < n; ++i) { // 控制树的横向遍历
            path.emplace_back(nums[i]);    
            backtrace(nums, i + 1);
            path.pop_back();
        }
    }
};

90. 子集 II

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    int n;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        n = nums.size();
        sort(nums.begin(), nums.end());
        backtrace(nums, 0);
        return result;
    }

    void backtrace(vector<int>& nums, int idx) {
        result.emplace_back(path);
        if (path.size() == n) {
            return;
        }
        for (int i = idx; i < n; ++i) { // 控制树的横向遍历
            if (i > idx && nums[i] == nums[i - 1]) {  // 好叭,我承认我是死记硬背的
                continue;
            }
            path.emplace_back(nums[i]);               
            backtrace(nums, i + 1);
            path.pop_back();    
        }
    }
};

排列问题

46. 全排列

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    int n;
    vector<vector<int>> permute(vector<int>& nums) {
        n = nums.size();
        vector<int> used(n);
        backtrace(nums, used);
        return result;
    }

    void backtrace(const vector<int>& nums, vector<int>& used) {
        if (path.size() == n) {
            result.emplace_back(path);
            return;
        }
        for (int i = 0; i < n; ++i) { // 控制树的横向遍历
            if (!used[i]) {     // 没有被访问
                used[i] = 1;
                path.emplace_back(nums[i]);
                backtrace(nums, used);
                path.pop_back();
                used[i] = 0;
            }
        }
    }
};

47. 全排列 II

class Solution {
public:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path;   // 用来存放符合条件的结果
    int n;
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        n = nums.size();
        sort(nums.begin(), nums.end()); // 去重步骤1
        vector<int> used(n);
        backtrace(nums, used);
        return result;
    }

    void backtrace(const vector<int>& nums, vector<int>& used) {
        if (path.size() == n) {
            result.emplace_back(path);
            return;
        }
        for (int i = 0; i < n; ++i) { // 控制树的横向遍历
            // 去重步骤2
            if (i > 0 && nums[i-1] == nums[i] && !used[i-1]) {
                continue;
            }
            if (!used[i]) {     // 没有被访问
                used[i] = 1;
                path.emplace_back(nums[i]);
                backtrace(nums, used);
                path.pop_back();
                used[i] = 0;
            }
        }
    }
};

棋盘问题

51. N 皇后

37. 解数独

其他

491. 递增子序列

332. 重新安排行程

总结


标签:nums,int,backtrace,vector,result,回溯,path
From: https://blog.51cto.com/u_10975123/6598548

相关文章

  • 算法——DFS、BFS、记忆回溯、记忆搜索
    回溯和深度优先搜索的区别回溯是一种更通用的算法。可以用于任何类型的结构,其中可以消除域的部分——无论它是否是逻辑树。深度优先搜索是与搜索树或图结构相关的特定回溯形式。它使用回溯作为其使用树的方法的一部分,但仅限于树/图结构。回溯和DFS之间的区别在于回溯处理隐......
  • 回溯算法
    回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案。回溯法解决的问题组合问题:N个数里面按一定规则找出k个数的集合切割问题:一个字符串按一定规则有几种切割方式子集问题:一个N个数的集合里有多少符合条件的子集排列问题:N个数按一定规则全排列,有几种排列方式棋盘问题:N皇后......
  • 22.回溯算法
    1.回溯的基本原理  在问题的解空间中,按深度优先遍历策略,从根节点出发搜索解空间树。算法搜索至解空间的任意一个节点时,先判断该节点是否包含问题的解。如果确定不包含,跳过对以该节点为根的子树的搜索,逐层向其祖先节点回溯,否则进入该子树,继续深度优先搜索。  回溯法解问题的......
  • 【计算机算法设计与分析】6-5 最小重量机器设计问题(C++_回溯法/分支限界法)
    问题描述设某一机器由n个部件组成,每一种部件都可以从m个不同的供应商处购得。设wij是从供应商j处购得的部件i的重量,cij是相应的价格。设计一个优先队列式分支限界法,给出总价格不超过d的最小重量机器设计。对于给定的机器部件重量和机器部件价格,设计一个优先队列式分......
  • 代码随想录Day24|回溯算法+JAVA大作战
     今日任务39. 组合总和40.组合总和II131.分割回文串 93.复原IP地址  78.子集   90.子集II   39.组合总和classSolution{List<List<Integer>>ans=newArrayList<>();LinkedList<Integer>now_ans=newLinkedList<>();publicLi......
  • 回溯01
     回溯法,又称回溯搜索法,是一种搜索方法,常用于解决树形或图形问题。回溯法通常使用递归来实现,在递归过程中不断尝试各种可能的解决方案,如果发现当前的解决方案不可行,就回溯到上一步,换一种方案继续尝试。、 ......
  • 代码随想录算法训练营第24天 | ● 理论基础 ● 77. 组合 - 第7章 回溯算法part01
     第七章 回溯算法part01今日内容: ●  理论基础 ●  77. 组合    详细布置   理论基础  其实在讲解二叉树的时候,就给大家介绍过回溯,这次正式开启回溯算法,大家可以先看视频,对回溯算法有一个整体的了解。 题目链接/文章讲解:https://programmercar......
  • 代码随想录算法训练营第25天 | ● 216.组合总和III ● 17.电话号码的字母组合 - 第7章
     第七章 回溯算法part02 今日内容:  ●  216.组合总和III●  17.电话号码的字母组合  详细布置   216.组合总和III  如果把 组合问题理解了,本题就容易一些了。  题目链接/文章讲解:https://programmercarl.com/0216.%E7%BB%84%E5%90%88%E6%80%B......
  • 【技术积累】算法中的回溯算法【一】
    回溯算法是什么回溯算法是一种用于求解在某个搜索空间中的问题的算法。它基本思想是从问题的某一种状态开始不断地尝试各种可能的选择,直到找到一种满足问题要求的解或者发现这些选择都无法满足要求时,就回到上一个状态,尝试其他的选择。回溯算法通常采用递归的方法实现,它会不断地......
  • 递归、分治、动态规划、贪心、回溯、分支限界
    递归、分治、动态规划、贪心、回溯、分支限界 相似算法比较:递归、分治、动态规划、贪心、回溯、分支限界​ 在学习算法的过程中,递归、分治、动态规划、贪心、回溯、分支限界这些算法有些类似,都是为了解决大问题,都是把大问题拆分成小问题来解决,但她们之间还是有一些不同之......