【代码随想录训练营第42期 Day22打卡 回溯Part1 - LeetCode 77. 组合 216.组合总和III 17.电话号码的字母组合

news2024/11/16 9:48:15

目录

一、做题心得

二、回溯基础知识

1.定义

2.适用问题

3.一个思想

4.代码实现

三、题目与题解

题目一:77. 组合 

题目链接

题解:回溯

 题目二:216.组合总和III

题目链接

题解:回溯

 题目三:17.电话号码的字母组合 

题目链接

 题解:回溯

四、小结

一、做题心得

今天是代码随想录打卡的第22天,也是成功来到了回溯章节。回溯的话,其实之前二叉树递归思路讲解的时候也有提到,回溯基本就是基于递归之下的一个实现。今天的题应该算是很经典的回溯的应用了:组合问题。作为回溯的入门,今天也是通过做题理解到了很多相关的知识,还有就是,模板的使用。

话不多说,直接开始今天的内容。

二、回溯基础知识

1.定义

回溯算法(Backtracking Algorithm)是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化来丢弃该解,即“回溯”到上一步,并尝试另一个候选解。这个过程一直进行,直到找到所有解或确定无解为止。

2.适用问题

回溯算法常用来解决那些可以分解为多个步骤或子问题的复杂问题,特别是在这些子问题之间存在相互依赖关系,且每个子问题的解空间可以明确地枚举出来时。它特别适用于寻找问题的所有解,而不是单一最优解的情况。

比如:

  1. 排列组合问题:如全排列、组合、子集、幂集等问题,可以通过递归地尝试每个可能的元素来构建解。

  2. 分割问题:如将集合划分为满足特定条件的子集,这类问题可以通过回溯来尝试不同的划分方式。

  3. 棋盘问题:如八皇后问题、N皇后问题、骑士巡逻(骑士在棋盘上遍历每个格子恰好一次)等,这类问题涉及在二维空间上放置或移动对象以满足特定条件。

  4. 图的着色问题:给定一个图,要求用最少的颜色给图中的每个顶点着色,使得任意两个相邻的顶点颜色不同。

  5. 布尔满足性问题(SAT):确定是否存在一组布尔变量的赋值,使得给定的布尔表达式为真。

  6. 子集和问题:从给定的整数数组中找出所有可能的子集,使得子集中的元素之和等于一个特定的目标值。

  7. 路径寻找问题:如迷宫问题、旅行商问题(TSP)的近似解可以通过回溯法得到(虽然TSP的精确解通常使用动态规划或其他更高效的算法)。

  8. 字符串处理问题:如字符串的排列、生成所有可能的括号组合等。

  9. 决策树/游戏树的遍历:在决策制定过程中,如棋类游戏或任何需要评估多个可能选择并作出最佳决策的场景中,回溯算法可以用来遍历所有可能的决策路径。

回溯法的本质还是枚举,效率并不高,但是为了解决以上这些问题,回溯依旧是最优的选择。

3.一个思想

回溯的搜索遍历过程类似于对树的搜索遍历

回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

树的宽度我们可以通过横向遍历实现:for循环

树的深度我们可以通过纵向遍历实现:递归

4.代码实现

回溯算法的实现往往采取同一套适用的模板,每个人的做题习惯不同,可能会存在一些差异,但是终归做题的思路是无异的。这里给出一套代码随想录回溯部分的模板:

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

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

以上模板的具体含义以及如何通过这个模板实现解决各种问题,我将会在后边几道题中提到。

三、题目与题解

题目一:77. 组合 

题目链接

77. 组合 - 力扣(LeetCode)

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

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

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

提示:

  • 1 <= n <= 20
  • 1 <= k <= n
题解:回溯

作为回溯章节的第一道题,也是解决组合问题中最经典的一道题,我们可以通过这道题初步感受一下回溯是如何实现的。

  1. 定义全局变量
    • ans:一个二维向量,用于存储所有生成的组合结果。每个内部向量代表一个组合。
    • vec:一个一维向量,用于在回溯过程中临时存储当前正在构建的组合。
  2. 回溯函数:backtrack(int n, int k, int start)
    • 参数 n 表示可选数字的上限(即从1到n中选择)。
    • 参数 k 表示每个组合中应包含的元素数量。
    • 参数 start 表示当前搜索的起始位置,用于避免生成重复的组合并优化搜索空间。
  3. 终止条件
    • 当vec的大小等于k时,说明已经找到了一个符合条件的组合,将其加入到ans中,并返回上一层递归(即return)。
  4. 递归搜索与剪枝
    • 使用一个循环从 start 开始遍历到 n - (k -  vec.size()) + 1。这里的剪枝操作 n - (k -  vec.size()) + 1 是为了提前结束不必要的搜索,因为如果剩余可选的数字不足以构成长度为 k 的组合,那么就没有必要继续搜索。
    • 在循环内部,将当前数字 i 添加到 vec 中,然后递归调用 backtrace 函数,并将搜索的起始位置设为 i + 1(避免重复使用同一个数字)。
    • 递归返回后,通过vec.pop_back()撤销vec中的最后一个元素,实现回溯,以便尝试其他可能的组合。
  5. 主函数:combine(int n, int k)
    • 初始化ans和vec,并调用backtrace函数从数字1开始构建组合。
    • 返回所有生成的组合结果ans

我对代码也进行了详细注释,代码如下:

class Solution {
public:
    vector<vector<int>> ans;            //用于存放所有生成的组合结果
    vector<int> vec;                    //用于存放当前正在构建的组合
    void backtrack(int n, int k, int start) {           //回溯函数,用于递归地生成组合
        if (vec.size() == k) {              //终止条件:组合的大小等于k
            ans.push_back(vec);
            return;                  //找到了一个有效组合,返回上一级递归
        }
        for (int i = start; i <= n - (k - vec.size()) + 1; i++) {          //从start开始遍历,i表示某次搜索的起始位置(注意剪枝操作的优化)
            vec.push_back(i);               //处理节点
            backtrack(n, k, i + 1);        //递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
            vec.pop_back();             //回溯,撤销处理的节点
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtrack(n, k, 1);         //调用回溯函数从数字1开始构建组合  
        return ans;
    }
};

当然,剪枝操作主要是为了降低复杂度,如果实在想不到剪枝,也可以直接 for 循环从 i = start 到 i = n。

 题目二:216.组合总和III

题目链接

216. 组合总和 III - 力扣(LeetCode)

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

  • 只使用数字1到9
  • 每个数字 最多使用一次 

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

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:

输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60
题解:回溯

 这个题有了上一道题的基础其实就比较简单了,套我们提到的代码实现的模板。个人感觉唯一和上一道题不同的就是终止条件的地方了--即 if 判断语句那里。

这里先看看我自己写的代码(个人感觉更好理解,毕竟就是上一道题的变形,模板的套用):

 并没有想到具体的剪枝的操作,只是套用模板照猫画虎给整出来了,还有击败100%,有点意外。

这里我们看看代码随想录运用到了剪枝的代码:

class Solution {
public:
    vector<vector<int>> result; // 存放结果集
    vector<int> path; // 符合条件的结果
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (sum > targetSum)        return;         //剪枝操作
        if (path.size() == k) {
            if (sum == targetSum)       result.push_back(path);
            return;             // 如果path.size() == k 但sum != targetSum 直接返回
        }
        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
            sum += i;               // 处理
            path.push_back(i);           // 处理
            backtracking(targetSum, k, sum, i + 1);         // 注意i+1调整startIndex
            sum -= i;                   // 回溯
            path.pop_back();             // 回溯
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(n, k, 0, 1);
        return result;
    }
};

 题目三:17.电话号码的字母组合 

题目链接

17. 电话号码的字母组合 - 力扣(LeetCode)

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

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

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。
 题解:回溯

这道题的话,看上去就要难不少了。

其实题意挺简单,就是将给定的字符串中的数字全部转为它们可以表示的字母替换:这里需要注意的是,0和1并没有对应的字母,而其他数字分别对应着3个字母即对应着三种情况。

这其实也是组合问题的一种变形,我们首先就是要想到使用回溯。回溯的话,我们套用上述的模板,想清楚终止条件,横向遍历,纵向遍历等等。当然这道题还有一个关键点就是哈希表(哈希映射)的使用,我们要通过哈希表将每个数字对应的字母(由于一个数字对应多个字母,就会是字符串型)存储下来,以便于后续的转换。

class Solution {
public:
    vector<string> ans;         //用于存储所有可能的字母组合(结果)
    string tmp;               //用于构建存储当前字母组合
    vector<string> hash = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};      //哈希(映射)表,将数字映射到对应的字母字符串,注意0和1无效,对应的字母为空
    void backtrace(int pos, string digits) {        //回溯--用于递归地生成所有可能的字母组合 
        if (pos == digits.size()) {             //终止条件:字符串中所有数字都已经处理完毕
            ans.push_back(tmp);
            return;
        }
        int num = digits[pos] - '0';    //取出当前位置(pos:从0开始)的数字(但以字符的形式),并转换为对应的索引num
        for (int i = 0; i < hash[num].size(); i++) {        //for循环:横向遍历字符串中每个字符
            tmp.push_back(hash[num][i]);        //处理当前位置
            backtrace(pos + 1, digits);     //递归:纵向遍历--注意从下个位置pos + 1开始
            tmp.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0)     return ans;         //给定字符串为空
        backtrace(0, digits);
        return ans;
    }
};

四、小结

今天的打卡到此也就结束了,后续回继续进行回溯的相关练习。最后,我是算法小白,但也希望终有所获。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1989914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企元数智百年营销史的精粹:借鉴历史创造未来商机

随着时代的发展和科技的进步&#xff0c;传统营销方式正在经历前所未有的颠覆和改变。在这个数字化时代&#xff0c;企业需要不断创新&#xff0c;同时借鉴百年营销史的精粹&#xff0c;汲取历史经验&#xff0c;创造未来商机。而"企元数智"作为现代营销的代表&#…

骑行激情,燃动巴黎——维乐Angel Revo坐垫,赋能你的奥运梦想!

当奥林匹克圣火在塞纳河畔熊熊燃烧&#xff0c;巴黎的街头巷尾都弥漫着骑行的激情。2024年的夏天&#xff0c;自行车赛道上&#xff0c;每一圈轮毂的转动都凝聚着运动员的汗水与荣耀。金牌赛程已定&#xff0c;女子公路自行车决赛于7月27日20:30鸣枪&#xff0c;男子紧随其后&a…

【Nuxt】编写接口和全局状态共享

编写接口 ~/server/api/homeInfo.get.ts export default defineEventHandler((event) > {return {code: 200,data: {name: hello world}} })服务端有一些方法可以快速获取请求常见字段&#xff1a; getQuery(event)getMethod(event)await readBody(event)await readRawBo…

配置Cuttlefish 虚拟 Android 设备

google 参考资料&#xff1a; https://source.android.com/docs/setup/start?hlzh-cn https://source.android.com/docs/devices/cuttlefish/get-started?hlzh-cn Cuttlefish 开始 验证 KVM 可用性 Cuttlefish 是一种虚拟设备&#xff0c;依赖于宿主机上可用的虚拟化。 …

鸿蒙Harmony开发:通用焦点样式事件规范

基础概念 焦点、焦点链和走焦 焦点&#xff1a;指向当前应用界面上唯一的一个可交互元素&#xff0c;当用户使用键盘、电视遥控器、车机摇杆/旋钮等非指向性输入设备与应用程序进行间接交互时&#xff0c;基于焦点的导航和交互是重要的输入手段。焦点链&#xff1a;在应用的组…

Docker安装teslamate

要求 Docker&#xff08;如果不熟悉 Docker&#xff0c;请参阅安装 Docker 和 Docker Compose&#xff09;一台始终开启的机器&#xff0c;因此 TeslaMate 可以持续获取数据计算机上至少有 1 GB 的 RAM 才能成功安装。外部互联网访问&#xff0c;与 tesla.com 交谈 创建一个名…

【数据结构】队列篇

文章目录 1.队列1.1 队列的概念及结构 2. 队列的实现2.1 准备工作2.2 队列的初始化2.3 队尾入队列2.4 队头出队列2.5 获取队列头部元素2.6 获取队列队尾元素2.7 获取队列有效元素个数2.8 检测队列是否为空2.9 销毁队列 3. 代码整合 1.队列 1.1 队列的概念及结构 队列&#xff…

黑马Java零基础视频教程精华部分_15_基本查找/顺序查找、二分查找/折半查找、插值查找、斐波那契查找、分块查找、哈希查找

系列文章目录 文章目录 系列文章目录一、基本查找/顺序查找核心思想&#xff1a;从0索引开始挨个往后查找代码&#xff1a;练习&#xff1a;定义一个方法利用基本查找&#xff0c;查询某个元素在数组中的索引&#xff0c;数组包含重复数据。 二、二分查找/折半查找核心思想:属于…

LVS多模式集群攻略!

NAT模式下的lvs集群 lvs-nat概念&#xff1a;修改请求报文的目标IP,多目标IP的DNAT&#xff0c;本质是多目标IP的DNAT&#xff0c;通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发 最终实现效果&#xff1a; 1.Director 服务器采用双网卡&#xff…

Qt入门(二):第一个Qt项目

新建项目 打开Qt Creator&#xff0c;新建项目&#xff0c;然后一路next 到这一步baseclass有三种选择&#xff1a; QMainWindow&#xff1a;主窗口基类&#xff0c;相较于QWidget&#xff0c;多了菜单栏等杂七杂八的东西。QWidget&#xff1a;最基础的窗口基类&#xff0…

编译运行 Byconity

我的系统是centos&#xff0c;因此用他们的docker编译并用他们的docker-compose运行&#xff0c;以下流程亲测可跑&#xff1a; 拉取并编译 https://github.com/ByConity/ByConity/tree/master/docker/debian/dev-env 运行 https://github.com/ByConity/ByConity/blob/master/d…

Matplotlib | 一文搞定Matplotlib从入门到实战演练!

文章目录 1 什么是Matplotlib1.1 Matplotlib的安装1.2 Matplotlib的基本使用 2 绘制直线3 绘制折线设置标签文字和线条粗细设置中文标题风格的设置 4 绘制曲线绘制曲线yx^2绘制正弦曲线和余弦曲线画布分区 5 绘制散点图绘制不同种类不同颜色的线 6 绘制条形图&#xff08;柱状&…

代码之外的生存指南——生产力

自己感觉今天都没有喝一口水的时间忙忙碌碌的工作了一天&#xff0c;但是到快下班或晚上回想一下今天自己到底在忙些什么的时候&#xff0c;却好像也没有做些什么对自己工作主线有意义的事情&#xff0c;大多时候时间都花费在了那些检查邮件、泡茶、拨打电话、开会议等干扰内容…

科普文:业务场景之常见10家HIS厂商概叙

智慧医院信息化经过几十年的发展&#xff0c;涌现出了一大批优秀的建设企业&#xff0c;我们选取了市面上部分主流的HIS厂商进行了汇集&#xff0c;包括厂商的发展情况、产品情况、技术情况、案例情况等。 卫宁健康 WINEX 口号&#xff1a;软件认知医疗 最新产品&#xff1a;…

集智书童 | 浙江大学 蚂蚁集团提出 PAI,一种无需训练减少 LVLM 幻觉的方法 !

本文来源公众号“集智书童”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;浙江大学 & 蚂蚁集团提出 PAI&#xff0c;一种无需训练减少 LVLM 幻觉的方法 &#xff01; 浙江大学 & 蚂蚁集团提出 PAI&#xff0c;一种无需训…

BERT预训练

一、动机 1、在NLP中的迁移学习中&#xff0c;使用预训练好的模型抽取词、句子的特征&#xff0c;不更新预训练好的模型&#xff0c;而是在需要构建新的网络来抓取新任务需要的信息&#xff0c;也就是最后面加上一个MLP做分类&#xff1b; 2、由于基于微调的NLP模型&#xff…

21. 合并两个有序链表(递归)

目录 一;题目&#xff1a; 二代码; 三&#xff1a;结果&#xff1a; 一;题目&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 二代码; /*** Definition for singly-linked list.* struct ListNode {* …

HTMX 和 FastAPI 绝佳搭配

FastAPI的优势 FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于基于标准 Python 类型提示使用 Python 3.7 构建 API。以下是它的一些主要优点&#xff1a; 性能&#xff1a;FastAPI 基于 Starlette 和 Pydantic 构建&#xff0c;使其与 …

Linux 中 core dump 异常的分析

目录 一、概述二、发生 core dump 的原因1. 空指针或非法指针2. 数组越界或指针越界3. 数据竞争 三、分析 core dump 的方法1. 启用 core dump2. 触发 core dump2.1 因空指针解引用而崩溃2.2 通过 信号触发 core dump 3. 利用 gdb 分析 core dump 一、概述 在 UNIX 系统中&…

sqli-labs第一关详细解答

首先判断是否有注入点 发现and 11 和 and 12结果一样&#xff0c;所以应该是字符型注入&#xff0c;需要对单引号做闭合 做闭合后发现报错&#xff0c;提示Limit 0,1&#xff0c;那就说明存在注入点&#xff0c;但是要注释掉后面的limit 0,1 使用--注释掉limit 0,1后&#xff…