Studying-代码随想录训练营day22| 回溯理论基础、77.组合、216.组合总和II、17.电话号码的字母组合

第22天,回溯章节开始!一大算法难点,加油加油!

回溯理论基础组合问题的剪枝操作

文档讲解:代码随想录回溯理论基础

视频讲解:回溯理论基础

回溯法也叫回溯搜索法,它是一种搜索,遍历的方式。回溯是递归的副产品,只要有递归就会有回溯,回溯本质就是递归过程中,到达底端(终止条件)后的不断返回过程。

回溯法的效率:回溯法的本质是穷举,它的效率并不高,是一种暴力解法,但有些时候我们就是需要这种暴力的解法,再适当增加剪枝解决问题。

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

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

这些问题都不好解决,N值较小的情况还能够通过for循环进行遍历,但一旦N值增大,for循环就难以进行编写了,而且在我们无法确定要循环多少层的时候,也无法使用for循环。这个时候就需要使用回溯的方法递归的进行了。

回溯法的结构:回溯法解决的问题都可以抽象为n叉树的树形结构。例如在求集合的问题中,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。因此在运用回溯法解题的时候,我们都可以通过画一个树状图来更好的理解递归的逻辑。

回溯法的模板:回溯法在进行编写的时候,主要需要注意三部,与递归三部曲相同的回溯三部曲。

  • 回溯函数模板返回值以及参数:回溯算法中返回值一般为void,参数列表需要根据你的递归逻辑来一个个确定参数。
void backtracking(参数)
  • 回溯函数终止条件:即满足一定的条件,把答案存储下来,并结束本层递归。回溯函数中肯定需要有终止条件,不能陷入无止尽的递归,那样会导致栈溢出。因此回溯构成的树形结构一定是一个高度有限的树。
if (终止条件) {
    存放结果;
    return;
}
  • 回溯搜索的遍历过程:回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

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

for循环就是遍历集合区间,可以理解为一个节点有多少孩子,这个for循环就执行多少次。

backtracking这里自己调用自己,实现递归。从图中可以看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。

总结模板如下:

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

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

77.组合

文档讲解:代码随想录组合

视频讲解:手撕组合问题、

题目:

学习:本题是回溯算法的第一道题,也是利用回溯算法经典的题目之一。显然对于第一个示例n = 4,k = 2的情况来说,我们可以通过两个for循环很容易的就能得到所有的组合结果,但是当n,k不断增大,至少需要k个for循环才能够遍历所有的节点,直接进行顺序代码编写的时候根本无法进行,因此本题需要采用回溯算法,事实上就是通过递归的方式来进行for循环,本质就是一层递归就是一层for循环。用树形结构来解释本题的回溯过程,如下图:

代码: 

//时间复杂度O(n*2^n)
//空间复杂度O(n)
class Solution {
public:
    //设置全局变量,减少参数数量,避免引用
    vector<vector<int>> result;
    vector<int> path;
    //确定回溯参数,由于是组合问题,组合中元素不存在顺序,因此还需要一个下标来指示当前遍历的位置
    void backtracking (int n, int k, int idnex) {
        //确定回溯终止条件
        if(path.size() == k) {
            result.push_back(path);
            return;
        }
        //单层递归逻辑
        for (int i = idnex; i <= n; i++) {
            path.push_back(i);
            backtracking(n, k, i + 1);
            //回溯处理
            path.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

本题还可以进行剪枝处理,显然上题中取4这一步是不需要的,因为已经构不成两个数了。当n=4,k=4中不需要的步骤会显得更多:

图中每一个节点就代表本层的for循环,剪枝的关键在于如何确定本层for循环的终点位置。

显然当for循环选择的位置之后的元素个数,不足我们需要的元素个数的时候,就没有必要搜索了。本题中已经选择的元素个数为path.size(),所需需要的元素个数为k - path.size(),列表中剩余元素个数(n - i)需要大于等于所需要的元素个数k - path.size()。因此i最多遍历到n - (k - path.size()) + 1的位置,也即 i <= n - (k - path.size()) + 1,为什么有个+1因为我们要把当前元素加上,这个通过例子模拟一下就能够理解。

因此优化后的for循环应该是:

for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置

优化后的代码为:

class Solution {
public:
    //设置全局变量,减少参数数量,避免引用
    vector<vector<int>> result;
    vector<int> path;
    //确定回溯参数,由于是组合问题,组合中元素不存在顺序,因此还需要一个下标来指示当前遍历的位置
    void backtracking (int n, int k, int idnex) {
        //确定回溯终止条件
        if(path.size() == k) {
            result.push_back(path);
            return;
        }
        //单层递归逻辑
        for (int i = idnex; i <= n - (k - path.size()) + 1; i++) {
            path.push_back(i);
            backtracking(n, k, i + 1);
            //回溯处理
            path.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

总结:画图会更有助于我们进行算法的剪枝处理。 


216.组合总和II 

文档讲解:代码随想录组合总和III

视频讲解:手撕组合总和III

题目: 

学习:本题与上题的不同在于遍历的集合确定了是1到9,n和k共同构成了遍历过程中需要判断的条件。本题中k相当于树的深度,9就是树的宽度,假如k=2,n=4用树形结构来表示就是:

代码: 回溯三部曲

//时间复杂度O(n*2^n)
//空间复杂度O(n)

class Solution {
public:
    vector<vector<int>> result; // 存放结果集
    vector<int> path; // 符合条件的结果
    void backtracking(int n, int k, int val, int index) { //val已经收集的元素的总和,也就是path里元素的总和
        //终止条件
        if(path.size() == k) {
            if(val == n) {
                result.push_back(path);
            }
            return; // 如果path.size() == k 但sum != targetSum 直接返回
        }
        //单层递归逻辑
        for(int i = index; i <= 9 - (k - path.size()) + 1; i++) {
            val += i;
            path.push_back(i);
            backtracking(n, k, val, i + 1); //注意调整index遍历的集合范围
            path.pop_back();  //回溯
            val -= i;  //回溯
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(n, k, 0, 1);
        return result;
    }
};

本题同样可以进行剪枝处理,例如上图中,后面有很大部分就不需要进行。

本题可以进行两部分的剪枝。1.首先适合上一道题相同的,依据k进行的集合大小的剪枝,也就是i只能遍历到9 - (k - path.size()) + 1;2.我们可以根据n进行剪枝,当加和val大于n的时候,就可以返回了,不需要继续进行集合后面数字的遍历,因为后面的数字加进来也一定会大于n,已经超出了我们所需要的值。

依据上述两点,代码进行剪枝处理后:

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int n, int k, int val, int index) {
        if(path.size() == k) {
            if(val == n) {
                result.push_back(path);
            }
            return;
        }
        //两个剪枝操作
        //9 - (k - path.size()) + 1 对范围剪枝,不够k个元素就不用遍历
        for(int i = index; i <= 9 - (k - path.size()) + 1; i++) {
            val += i;
            //值剪枝,大于n就不用遍历了
            if(val > n) {
                return;
            }
            path.push_back(i);
            backtracking(n, k, val, i + 1);
            path.pop_back();
            val -= i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(n, k, 0, 1);
        return result;
    }
};

17.电话号码的字母组合

文档讲解:代码随想录电话号码的字母组合

视频讲解:手撕电话号码的字母组合

题目:

学习:显然本题也是一个不断循环匹配的过程。依据题干可以把本题分解为3步:1.拆分digits中的数字,并使数字与对应的字母串映射匹配;2.根据顺序依次进行循环遍历。3.保存结果集。例如依据”23“示例,我们可以给出回溯对应的树形图:

对于第1个问题,我们可以通过设置一个哈希表或者map来完成每个数字对字母串的映射,因为本题中数字数量有限且是顺序排列的,因此可以用一个数组来完成映射。

对于2,3问题,就是使用回溯算法来进行模拟遍历了。 

代码: 确定回溯三部曲。

//时间复杂度: O(3^m * 4^n),其中 m 是对应三个字母的数字个数,n 是对应四个字母的数字个数
//空间复杂度: O(3^m * 4^n)
class Solution {
private:
    //使用一个数组来充当哈希表,对应每个字母
    string letter[10] = {" ", " ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public:
    vector<string> result;
    string s;
    //确定返回值和参数,参数中需要一个index来指示遍历到第几个数字了
    void backtracking(string& digits, int index) {
        //确定终止条件,当index--digits.size()时说明所有的数字都遍历完了,因为最后一个数字的下标为digits.size()-1
        if(index == digits.size()) {
            result.push_back(s);
            return;
        }
        //取出数字对应的字符串
        int dig = digits[index] - '0';
        string list = letter[dig];
        //确定单层递归逻辑
        for(int i = 0; i < list.size(); i++) {
            s.push_back(list[i]);
            backtracking(digits, index + 1);
            s.pop_back();
        }
        return; //可写可不写
    }
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0) {
            return result;
        }
        backtracking(digits, 0);
        return result;
    }
};

总结:

回溯算法是一个不好理解的算法,初期过程中可以通过画树形图的方式来加深理解!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 算法交易实验73 QTV200第二步: 数据清洗并写入ClickHouse

说明 先检查一下昨天启动的worker是否正常工作&#xff0c;然后做一些简单的清洗&#xff0c;存入clickhouse。 内容 1 检查数据 from Basefuncs import * # 将一般字符串转为UCS 名称 def dt_str2ucs_blockname(some_dt_str):some_dt_str1 some_dt_str.replace(-,.).re…

【日记】软考居然一次过了(620 字)

正文 早上空闲的时候&#xff0c;上 QQ 看了一下&#xff0c;许久不见动静的系统架构设计师群有人说出分了。我想高级都出分了&#xff0c;中级应该也出来了&#xff0c;于是用手机查了一下。看到分数几乎快要泪从中来。为什么软考能一次过&#xff0c;银行从业资格证考了两三…

放烟花短视频素材去哪里找?去哪里下载?烟花素材网分享

在当代社会&#xff0c;短视频凭借其独有的魅力成为大众传递情感、记录生活、分享快乐的新兴方式。特别是在庆祝节日和特殊时刻时&#xff0c;烟花的绚丽效果常常被用来吸引观众的目光&#xff0c;成为视频作品中的亮点。然而&#xff0c;对于短视频制作者来说&#xff0c;寻找…

【SCAU操作系统】期末复习简答及计算题例题解析

一、写出下列英文缩写词在计算机系统中的英文或中文全名。 OS: Operating System 操作系统PSW: Program Status Word 程序状态字FCFS: First Come First Serve 先来先服务PCB: Process Control Block 进程控制块DMA: Direct Memory Access 直接存储器存取MMU: Memory Manageme…

Does a vector database maintain pre-vector chunked data for RAG systems?

题意&#xff1a;一个向量数据库是否为RAG系统维护预向量化分块数据&#xff1f; 问题背景&#xff1a; I believe that when using an LLM with a Retrieval-Augmented Generation (RAG) approach, the results retrieved from a vector search must ultimately be presented…

从源码到上线:直播带货系统与短视频商城APP开发全流程

很多人问小编&#xff0c;一个完整的直播带货系统和短视频商城APP是如何从源码开发到最终上线的呢&#xff1f;今天&#xff0c;笔者将详细介绍这一全过程。 一、需求分析与规划 1.市场调研与需求分析&#xff1a;首先需要进行市场调研&#xff0c;了解当前市场的需求和竞争情…

Flutter学习:从搭建环境到运行

一、开发环境的搭建 本文所示内容都是在Windows系统下进行的。 1、下载 Flutter SDK Flutter 官网&#xff08;https://docs.flutter.cn/release/archive?tabwindows&#xff09; 或者通过 git clone -b master https://github.com/flutter/flutter.git 下载 2、配置环境…

VMware 最新的安全漏洞公告VMSA-2024-0013

#深度好文计划# 一、摘要 2024年6月26日&#xff0c;VMware 发布了最新的安全漏洞公告 VMSA-2024-0013&#xff0c;修复了 VMware ESXi 和 VMware vCenter 中的多个安全漏洞。 VMSA-2024-0013&#xff1a;VMware ESXi 和 vCenter Server 更新修正了多个安全性漏洞 &#xff…

datax入门(datax的安装与简单使用)——01

datax入门&#xff08;datax的安装与简单使用&#xff09;——01 1. 官网2. 工具部署&#xff08;通过下载DataX工具包&#xff09;2.1 下载、解压2.2 配置2.2.1 查看配置模版2.2.2 根据模版配置json2.2.3 启动DataX 3. datax的简单使用3.1 mysql2stream3.2 mysql2mysql3.2.1 拼…

评估大型语言模型生成文章的能力

1. AI解读 1.1. 总体概要 本文探讨了大型语言模型&#xff08;LLMs&#xff09;如GPT-4在生成特定领域&#xff08;如计算机科学中的自然语言处理NLP&#xff09;教育调查文章方面的能力和局限性。研究发现&#xff0c;尽管GPT-4能够根据特定指导生成高质量的调查文章&#x…

使用jupyter打开本地ipynb文件的方法

常用方法&#xff1a; 先启动jupyter&#xff0c;然后在打开的页面点击upload&#xff0c;选择想要打开的文件上传然后打开&#xff0c;但是这样其实是先复制了一份到jupyter中&#xff0c;然后打开运行。而我不想复制。 方法二 先打开项目文件所在文件夹&#xff0c;文件夹…

背靠广汽、小马智行,如祺出行打得过滴滴和百度吗?

©自象限原创 作者丨艾AA 编辑丨薛黎 北京时间6月14日凌晨&#xff0c;在特斯拉股东大会上&#xff0c;马斯克阐述了对Robotaxi&#xff08;自动驾驶出租车&#xff09;商业模式的构想——特斯拉不仅会运营自己的无人驾驶出租车车队&#xff0c;还可以让特斯拉车主们的爱…

Flutter学习目录

学习Dart语言 官网&#xff1a;https://dart.cn/ 快速入门&#xff1a;Dart 语言开发文档&#xff08;dart.cn/guides&#xff09; 学习Flutter Flutter生命周期 点击跳转Flutter更换主题 点击跳转StatelessWidget和StatefulWidget的区别 点击跳转学习Flutter中新的Navigato…

一文入门CMake

我们前几篇文章已经入门了gcc和Makefile&#xff0c;现在可以来玩玩CMake了。 CMake和Makefile是差不多的&#xff0c;基本上是可以相互替换使用的。CMAke可以生成Makefile&#xff0c;所以本质上我们还是用的Makefile&#xff0c;只不过用了CMake就不用再写Makefile了&#x…

Spring底层原理之bean的加载方式四 @import 注解

bean的加载方式四 import 第四种bean的导入方式 是import导入的方式 在配置类上面加上注解就行 package com.bigdata1421.config;import com.bigdata1421.bean.Dog; import org.springframework.context.annotation.Import;Import(Dog.class) public class SpringConfig4 {…

Linux高级编程——进程

1.进程的含义? 进程是一个程序执行的过程&#xff0c;会去分配内存资源&#xff0c;cpu的调度 PID, 进程标识符 当前工作路径 chdir umask 0002 进程打开的文件列表 文件IO中有提到 &#xff08;类似于标准输入 标准输出的编号&#xff0c;系统给0&#xff0c;1&#xf…

点云处理实战 点云平面拟合

目录 一、什么是平拟合 二、拟合步骤 三、数学原理 1、平面拟合 2、PCA过程 四、代码 一、什么是平拟合 平面拟合是指在三维空间中找到一个平面,使其尽可能接近给定的点云。最小二乘法是一种常用的拟合方法,通过最小化误差平方和来找到最优的拟合平面。 二、拟合步骤…

1.1章节print输出函数语法八种 使用和示例

1.打印变量和字符串 2-4.三种使用字符串格式化 5.输出ASCLL码的值和中文字符 6.打印到文件或其他对象&#xff08;而不是控制台&#xff09; 7.自定义分隔符、和换行符和结束符 8.连接符加号连接字符串 在Python中&#xff0c;print() 函数用于在控制台上输出信息。这是一个非常…

设置日历程序

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 namespace 设置日历 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void dateTimePicker1_ValueChanged(object sender, EventArgs e){richTextBox1.Text dateTimePicker1.T…