【代码随想录算法训练营14期】- day 24-30 回溯算法


day 24今日内容:

● 理论基础
● 77. 组合

24理论基础

回溯法,一般可以解决如下几种问题:

组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等

可解决问题

代码

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案

解题思路

n个数返回k个数组合,开始看例子使用二重循环写出了k=2的答案,从i开始for循环,二重循环从i+1循环,但是k=20就要写20个循环,每次循环都是一样的操作,只是开头不同,故采用回溯算法,每次for循环放入一个数,递归直到答案有k个返回,满足条件以后要移除最后的值,即回溯。

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> tmp;
    void backtracking(int n,int k,int start){
        //终止条件
        if(tmp.size() == k){
            ans.push_back(tmp);
            return;
        }
        //单层递归
        for(int i = start; i <= n; i++){
            tmp.push_back(i);
            backtracking(n, k, i + 1);
            tmp.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtracking(n,k,1);
        return ans;
    }
};

day 25今日内容:

● 216.组合总和III
● 17.电话号码的字母组合

216.组合总和III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

解题思路

组合总和在上述计算k个数集合的条件 多了sum;对sum变量进行判断是否等于n;每次遍历要加i,回溯要减i;

代码

class Solution {
public:
    vector<int> tmp;
    vector<vector<int>> ans;
    int sum;
    void backtrack(int k, int n,int start){
        if(n == sum && tmp.size() == k){
            ans.push_back(tmp);
            return;
        }
        for(int i = start; i <= 9; i++){
            sum += i;
            tmp.push_back(i);
            backtrack(k, n, i + 1);
            sum -= i;
            tmp.pop_back();
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        sum = 0;
        backtrack(k, n, 1);
        return ans;
    }
};

17.电话号码的字母组合

解题思路

电话号码组合和数字组合不同,数字回溯是数字开始增加,即第一次循环是1-9,第二次是2-9,第三次是3-9……电话号码第一次循环是第一个数对应的字符串,第二次循环是第二个数对应的字符串…………

代码

class Solution {
public:
    const string telmap[10] = {
        "",
        "",
        "abc",
        "def",
        "ghi",
        "jkl",
        "mno",
        "pqrs",
        "tuv",
        "wxyz",
    };
    vector<string> ans;
    string s;
    void backtrack(string digits,int index){
        if(s.size() == digits.size()){
            ans.push_back(s);
            return;
        }
        int num = digits[index] - '0';
        string tels = telmap[num];
        for(int i = 0; i < tels.size(); i++){
            s.push_back(tels[i]);
            backtrack(digits, index + 1);
            s.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        if(digits.size() == 0)
            return ans;
        backtrack(digits,0);
        return ans;
    }
};

● 39. 组合总和
● 40.组合总和II
● 131.分割回文串

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

解题思路

组合问题使用 回溯法,同一个数组可重复代表每次开始的位置即为start,第一次写的时候我忽略了每次开始变量,所以每次开始都是数组0开始,会有重复的答案。

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> tmp;
    int sum;
    void backtrack(vector<int>& candidates,int target,int start){
        if(sum > target)
            return;
        if(sum == target){
            ans.push_back(tmp);
            return;
        }
        for(int i = start; i < candidates.size(); i++){
            sum += candidates[i];
            tmp.push_back(candidates[i]);
            backtrack(candidates,target,i);
            sum -= candidates[i];
            tmp.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sum = 0;
        backtrack(candidates,target,0);
        return ans;

    }
};

40.组合总和II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

解题思路

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> tmp;
    int sum;
    void backtrack(vector<int>& candidates,int target,int start){
        if(sum > target)
            return;
        if(sum == target){
            ans.push_back(tmp);
            return;
        }
        for(int i = start; i < candidates.size(); i++){
            if(i > start && candidates[i] == candidates[i - 1])
                continue;
            sum += candidates[i];
            tmp.push_back(candidates[i]);
            backtrack(candidates,target,i + 1);
            sum -= candidates[i];
            tmp.pop_back();
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sum = 0;
        sort(candidates.begin(),candidates.end());
        backtrack(candidates,target,0);
        return ans;
    }
};

131.分割回文串

解题思路

分割回文串,分割问题可看作组合问题,for循环每个字母为开始,然后判断以某个字母开始满足条件的回文串;
判断回文串的函数是从头和尾判断两个字母是否相同;
用到取子串函数s.subsr(起始位置,长度)

代码

class Solution {
public:
    vector<vector<string>> ans;
    vector<string> tmp;
    void backtrack(string s, int start){
        if(start >= s.size()){
            ans.push_back(tmp);
            return;
        }
        for(int i = start; i < s.size(); i++){
            if(isMirror(s,start,i)){
                string str = s.substr(start,i - start + 1);
                tmp.push_back(str);
            }
            else
                continue;
            backtrack(s, i + 1);
            tmp.pop_back();
        }
    }
    bool isMirror(string s, int start, int end){
        for(int i = start, j = end; i <= j; i++, j--){
            if(s[i] != s[j])
                return false;
        }
        return true;
    }
    vector<vector<string>> partition(string s) {
        backtrack(s, 0);
        return ans;
    }
};

#28 **93.复原IP地址 **(二刷)
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 ‘.’ 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

解题思路

和分割回文串一样的思路,
判断条件改为对ip单个数字;1)IP单个数字当长度大于2时首字母不能为0;2)对每个字符判断在0-9之间👎单个数字大小小于255;
回溯条件结束情况要么段数大于4了即point > 3或者开始的位置大于s的长度即第三个点后没有字母了;当满足三个点以后还要判断最后一个ip字符是否满足要求再加入ans
然后就是回溯时数组插入函数insert(位置,插入的字符);插入字符以后新的i不再是i + 1,而是i+2;删除函数erase(删除的位置)

代码

class Solution {
public:
    vector<string> ans;
    int point;
    void trackback(string s, int start, int point){
        //判读条件
        if(start > s.size() - 1){
            return;
        } 
        if(point == 3){
            if(isIP(s,start,s.size()-1)){
                ans.push_back(s);
                return;
            } 
        }
        for(int i = start; i < s.size(); i++){
            if(isIP(s, start , i)){
                s.insert(s.begin() + i + 1, '.');
                point++;
                trackback(s, i + 2, point);
                point--;
                s.erase(s.begin() + i + 1);
            }
            else
                break;
        }
    }
    bool isIP(string s, int start ,int end){
        //判断start开始,end结束的s是否满足单个数要求
        int length = end - start + 1;
        if(length >= 2 && s[start] == '0'){
            return false;
        }
        int nums = 0;
        for(int i = start; i <= end; i++){
            if(s[i] > '9' || s[i] < '0')
                return false;
            nums = nums * 10 + (s[i] - '0');
            if(nums > 255){
                return false;
            }
        }
        return true;
    }
    vector<string> restoreIpAddresses(string s) {
        trackback(s,0,0);
        return ans;
    }
};

78.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

解题思路

子集是组合问题,子集是要回溯树的所有节点,故 没有判断条件,把路径放入答案即可;

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtrack(vector<int>& nums,int start){
        ans.push_back(path);
        for(int i = start; i < nums.size(); i++){
            path.push_back(nums[i]);
            backtrack(nums, i + 1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtrack(nums, 0);
        return ans;
    }
};

90.子集II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

解题思路

这里重要的是对去重,先对数组排序以后,对每次循环判断i大于start且前一个数与其相同就congtinue不进入循环;

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtrack(vector<int>& nums, int start){
        ans.push_back(path);
        for(int i = start; i < nums.size() ; i++){
            //去重
            if(start < i && nums[i] == nums[i - 1]){
                continue;
            }
            path.push_back(nums[i]);
            backtrack(nums, i + 1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        backtrack(nums, 0);
        return ans;
    }
};
  • 491.递增子序列
  • 46.全排列
  • 47.全排列 II

491.递增子序列

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

解题思路

去重不能对数组排序以后判断i大于start以后是否等于前一个数;
所有需要一个数组来记录这个数有没有被使用过
递归的结束的条件是大于等于两个数的数组就可以;
单层递归,path里有数,当前的数小于path最后一个数的大小或者path为空,这个数已经出现过都需要跳过循环;
if((!path.empty() && nums[i] < path.back()) || used[nums[i] + 100] == 1)

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtrack(vector<int>& nums,int start){
        if(path.size() >= 2){
            ans.push_back(path);
        }
        int used[201] = {0};
        for(int i = start; i < nums.size(); i++){
            //  1、path不空但数字不满足递增则下一个 2、该数字已经出现过;
            if((!path.empty() && nums[i] < path.back()) || used[nums[i] + 100] == 1){
                continue;
            }
            used[nums[i] + 100] = 1;
            path.push_back(nums[i]);
            backtrack(nums, i + 1);
            path.pop_back();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtrack(nums, 0);
        return ans;
    }
};

46.全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

解题思路

排列问题其实就是组合的有序,即回溯时开始的指标就没有了;递归终止条件就是收集元素等于数组元素个数时停止;单层逻辑需要使用一个used数组来存储该数字是否使用过,加入path前标记其为true;弹出以后要还原其为false

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtrack(vector<int>& nums, vector<bool>& used){
        if(path.size() == nums.size()){
            ans.push_back(path);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(used[i] == true){
                  continue;
            }
            used[i] = true;
            path.push_back(nums[i]);
            backtrack(nums,used);
            path.pop_back();
            used[i] = false;
        }     
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backtrack(nums, used);
        return ans;
    }
};

46.全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

解题思路

排序去重

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtrack(vector<int>& nums, vector<bool>& used){
        if(path.size() == nums.size()){
            ans.push_back(path);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false){
                continue;
            }
            if(used[i] == false){
                used[i] = true;
                path.push_back(nums[i]);
                backtrack(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<bool> used(nums.size(),false);
        backtrack(nums, used);
        return ans;
    }
};


代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值