首页 > 编程语言 >代码随想录DAY22 - 回溯算法 - 08/21

代码随想录DAY22 - 回溯算法 - 08/21

时间:2024-08-22 21:56:03浏览次数:14  
标签:21 组合 int 08 随想录 递归 result 回溯 composition

目录

理论回顾

什么是回溯法

回溯法的效率

回溯法解决的问题

如何理解回溯

组合

题干

思路和代码

递归法

递归优化:剪枝

组合总和Ⅲ

题干

思路和代码

递归法

递归优化

电话号码的字母组合

题干

思路和代码

递归法


理论回顾

什么是回溯法

回溯是一种类似枚举的搜索方法,回溯和递归相辅相成。

回溯法的效率

回溯法本质是穷举,也就是检索所有可能最后才找出结果,因此效率并不高。一般为了提高效率都会进行剪枝操作。

回溯法解决的问题

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

  • 组合问题:N个数里面按一定规则找出k个数的集合

  • 切割问题:一个字符串按一定规则有几种切割方式

  • 子集问题:一个N个数的集合里有多少符合条件的子集

  • 排列问题:N个数按一定规则全排列,有几种排列方式

  • 棋盘问题:N皇后,解数独等等

如何理解回溯

回溯解决的问题可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

组合

题干

题目:给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。

链接:. - 力扣(LeetCode)

思路和代码

递归法

将问题拆解为先选定组合中的一个数,先记录包含这个数且满足条件的组合,再递归从剩余的 n-1 个数中找组合。

  • 递归参数和返回值:递归参数是集合大小 n,组合大小 k,组合起始位置 startIndex;在递归过程中不断更新组合和结果集。

  • 递归结束的条件:当组合数组已经达到题目要求的组合大小,说明一个组合已经被找到,将该组合插入结果集后返回。

  • 递归顺序:先确定组合的起始元素 startIndex,再递归从剩余的 n - startIndex 个元素里继续填充组合。

class Solution {
public:
    vector<int> composition; // 记录组合
    vector<vector<int>> result; // 记录所有组合结果
    void backTracking(int n, int k, int startIndex){
        if (composition.size() == k){
            // 当组合大小已满足 k,说明已经找到一个组合,插入结果集
            result.push_back(composition);
            // 返回到上一层
            return;
        }
        for (int i = startIndex; i <= n; ++i) {
            composition.push_back(i); // 填充组合的一个位置,接下来要填充下一个位置
            backTracking(n,k,i+1); // 找起点为 j+1 的组合数
            composition.pop_back(); // 回溯的时候要记得弹出之前插入的数
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backTracking(n,k,1); // 起始从 1 开始
        return result;
    }
};
递归优化:剪枝

当集合中剩余的元素已经没法凑够组合大小时,就不必再进入循环找组合中了。也就是说,只有当 组合中已有的元素个数 + 集合中剩余的元素个数 ≥ 组合大小 时,即当 composition.size() + (n-i+1) >= k 时才需要进入循环。

class Solution {
public:
    vector<int> composition; // 记录组合
    vector<vector<int>> result; // 记录所有组合结果
    void backTracking(int n, int k, int startIndex){
        if (composition.size() == k){
            // 当组合大小已满足 k,说明已经找到一个组合,插入结果集
            result.push_back(composition);
            // 返回到上一层
            return;
        }
        for (int i = startIndex; composition.size()+(n-i+1) >= k; ++i) { // for 循环条件中进行剪枝
                                // 修改了进入循环的条件!!
            composition.push_back(i); // 填充组合的一个位置,接下来要填充下一个位置
            backTracking(n,k,i+1); // 找起点为 j+1 的组合数
            composition.pop_back(); // 回溯的时候要记得弹出之前插入的数
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backTracking(n,k,1); // 起始从 1 开始
        return result;
    }
};

组合总和Ⅲ

题干

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

  • 只使用数字 1 到 9

  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

链接:. - 力扣(LeetCode)

思路和代码

从 1~9 九个数字中选 k 个数组合,若组合之和满足 n ,则插入结果集。

递归法

在上一题的基础上,找出每一个组合,如果组合之和为 n,才插入结果集,否则直接返回。

class Solution {
public:
    int sum = 0; // 记录组合数之和
    vector<int> composition; // 记录组合
    vector<vector<int>> result; // 记录所有组合的结果
    void backTracking(int k, int n, int startIndex){
        if (composition.size() == k){
            if (sum == n){ // 当组合之和 sum 等于目标和 n 时,才插入结果集!
                result.push_back(composition);
            }
            return;
        }
        for (int i = startIndex; i <= 9; ++i) {
            composition.push_back(i);
            sum += i;
            backTracking(k,n,i+1);
            composition.pop_back();
            sum -= i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backTracking(k,n,1);
        return result;
    }
};
递归优化

原来的集合中包含 1~9 九个数字,且是升序的,而我们是从前往后、从小到大寻找组合,当组合之和 sum 已经大于目标和 n 时,说明后续的组合只会更大,不需要再进入循环中了。

class Solution {
public:
    int sum = 0; // 记录组合数之和
    vector<int> composition;
    vector<vector<int>> result;
    void backTracking(int k, int n, int startIndex){
        if (composition.size() == k){
            if (sum == n){
                result.push_back(composition);
            }
            return;
        }
        for (int i = startIndex; composition.size()+(9-i+1) >= k && sum+i <= n; ++i) { // 剪枝
            					// 同理,第一个剪枝操作是当剩余的元素已经凑不够组合大小时,停止循环
            					// 第二个剪枝操作是若 组合之和 已经大于 n 了,就没必要进入循环了
            composition.push_back(i);
            sum += i;
            backTracking(k,n,i+1);
            composition.pop_back();
            sum -= i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backTracking(k,n,1);
        return result;
    }
};

电话号码的字母组合

题干

题目:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

说明:

  • 0 <= digits.length <= 4

  • digits[i] 是范围 ['2', '9'] 的一个数字。也就是说不会输入除了 2 ~ 9 以外的字符

链接:. - 力扣(LeetCode)

思路和代码

首先要知道对应的数字都能映射哪些字母,那么就建立一个 map,键值对为 {数字,字符串(能映射的所有字母)}。这道题本质上也是找组合,只不过找的是不同字符的组合,且在找组合之前得先映射。

递归法

递归法的思路是将问题分解为:每次先确认传入的字符串中第一个数字对应的字符,再递归去确认字符串之后的数字对应的字符。

class Solution {
public:
    unordered_map<int,string> numsToChar{
        {2,"abc"},{3,"def"},{4,"ghi"},{5,"jkl"},
        {6,"mno"},{7,"pqrs"},{8,"tuv"},{9,"wxyz"}
    };
    string composition; // 存储组合
    vector<string> result; // 记录所有组合的结果
    void backTracking(string digits){
        if (digits.size() == 0){
            // 当传入的字符串为空,说明已经找到一个组合,插入结果集
            result.push_back(composition);
            return;
        }
        
        int num = digits[0]-'0'; // 每次都要确认第一个数字
        string s = numsToChar[num]; // 将数字映射为字符串
        for (char c : s) {
            composition.push_back(c); // 已经插入一个字符
            string newDigits;
            // 调整 digits,而不是改 startIndex
            if (digits.size() > 1) newDigits = digits.substr(1); // 要从剩余的字符串中寻找元素插入组合
            else newDigits = "";
            backTracking(newDigits); // 继续递归寻找元素
            composition.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return {};
        backTracking(digits);
        return result;
    }
};

标签:21,组合,int,08,随想录,递归,result,回溯,composition
From: https://blog.csdn.net/chan_lay/article/details/141438557

相关文章

  • 代码随想录DAY23 - 回溯算法 - 08/22
    组合总和题干题目:给你一个无重复元素的整数数组candidates和一个目标整数target,找出candidates中可以使数字和为目标数target的所有不同组合,并以列表形式返回。你可以按任意顺序返回这些组合。candidates中的同一个数字可以无限制重复被选取。如果至少......
  • sqlilabs less21-25关手工注入
    第21关一.登录页面二.BurpSuite抓包,进入重放器三.查询数据库先进行编码')andupdatexml(1,concat(1,database()),1)#四.查表,先进行编码')andupdatexml(1,concat(1,(selectgroup_concat(table_name)frominformation_schema.tableswheretable_schema='secur......
  • 代码随想录算法训练营第二十三天(回溯 二)
    力扣题部分:39.组合总和题目链接:.-力扣(LeetCode)题面:给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candid......
  • 代码随想录算法训练营第二十二天(回溯 一)
    开始学习回溯!回溯理论基础代码随想录文章链接:代码随想录文章摘要:什么是回溯法回溯法也可以叫做回溯搜索法,它是一种搜索的方式。在二叉树系列中,我们已经不止一次,提到了回溯。回溯是递归的副产品,只要有递归就会有回溯。所以以下讲解中,回溯函数也就是递归函数,指的都是一......
  • YC327B [ 20240821 CQYC NOIP 模拟赛 T2 ] 括号串(bracket)
    题意给定\(S\in\{(,),?\}\)。定义深度为括号嵌套的子序列的最大长度除以\(2\)。求出将\(?\)替换为括号的所有括号串的深度之和,对\(998244353\)取模。\(n\le10^6\)。Sol考虑如何把每次贡献只计算一次。不难想到在括号的中心点计算。可以发现,若当前左右括号......
  • H7-TOOL脱机烧录的UID加密操作方法,支持一键生成目标板C代码,方便大家轻松操作(2024-08-2
    UID加密使用比较方便,对应的C代码模板已经做好,使用TOOL上位机生成后,直接复制粘贴到自己的工程即可使用。返回1表示解密成功,返回0表示失败。【UID加密原理】1、烧录器在烧录芯片时,按照指定的算法将UID码编码为一个加密数据,并写入FLASH指定区域。2、用户的程序必须增加一段UID校......
  • 代码随想录Day23
    131.分割回文串给你一个字符串s,请你将s分割成一些子串,使每个子串都是回文串。返回s所有可能的分割方案。示例1:输入:s="aab"输出:[["a","a","b"],["aa","b"]]示例2:输入:s="a"输出:[["a"]]提示:1<=s.length<=16s仅由......
  • P4216[SCOI2015情报传递 树上主席树
    题意:维护一棵树,某些点有点权(没有则为正无穷),点权为正整数,查询树上路径点权小于等于某个值的点的个数。分析:考虑维护主席树,root[i]数组存储第i个节点到根的点权的权值线段树的树根。更具体地,把第i个节点到根的路径上的点权累积到权值线段树中,对一个询问x,y,记lca为z,查询值为k,答案a......
  • sqli-labs靶场通关攻略 21-25
    主页有sqli-labs靶场通关攻略1-20第二一关less-21步骤一:输入Username:adminPassword:admin利用Burpsutie进行抓包步骤二:在Cookie后输入'报错,判断闭合方式为')#对所写代码进行如下操作:选中右击->Convertselection->Base64->Base64-encode步骤三:判断列数')order......
  • 「代码随想录算法训练营」第四十四天 | 图论 part2
    200.岛屿数量题目链接:https://leetcode.cn/problems/number-of-islands/description/文章讲解:https://programmercarl.com/kamacoder/0099.岛屿的数量深搜.html题目难度:中等题目状态:看题解思路一:深搜版方法dfs:参数:接受一个字符网格grid和当前坐标(r,c)。功能:......