【CV炼丹师勇闯力扣训练营 Day22:§7 回溯1】

CV炼丹师勇闯力扣训练营

代码随想录算法训练营第22天


回溯法其实就是暴力查找,回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,一般可以解决如下几种问题:

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

回溯法解决的问题都可以抽象为树形结构。因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度
for循环横向遍历,递归纵向遍历,回溯不断调整结果集。

在这里插入图片描述

回溯三部曲

S1. 回溯函数模板返回值以及参数
void backtracking(参数)

S2. 回溯函数终止条件
什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归
if (终止条件) {
存放结果;
return;
}

S3. 回溯搜索的遍历过程
在这里插入图片描述
for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}

整体模板如下:

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

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

77 组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
输入:n = 1, k = 1 输出:[[1]]

在这里插入图片描述

S1 确定变量和参数
def backtracking(self, n, k, startIndex, path, result):
“”"
:param startIndex:记录本层递归的中,集合从哪里开始遍历
:param path: 用来存放符合条件结果
:param result: 存放符合条件结果的集合
“”"

S2 回溯终止条件
path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,在图中path存的就是根节点到叶子节点的路径。如图红色部分:
在这里插入图片描述
此时用result二维数组,把path保存起来,并终止本层递归。
if (path.size() == k) {
result.push_back(path);
return;
}

S3 单层搜索逻辑
回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。
for循环每次从startIndex开始遍历,然后用path保存取到的节点i。
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
path.pop_back(); // 回溯,撤销处理的节点
}

可以看出backtracking(递归函数)通过不断调用自己一直往深处遍历,总会遇到叶子节点,遇到了叶子节点就要返回。
backtracking的下面部分就是回溯的操作了,撤销本次处理的结果。

代码如下(Python3):

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        result = []  # 存放结果集
        self.backtracking(n, k, 1, [], result)
        return result
    def backtracking(self, n, k, startIndex, path, result):
        if len(path) == k:
            result.append(path[:])
            return
        for i in range(startIndex, n + 1):  # 需要优化的地方
            path.append(i)  # 处理节点
            self.backtracking(n, k, i + 1, path, result)
            path.pop()  # 回溯,撤销处理的节点

剪枝

这个遍历的范围是可以剪枝优化的,怎么优化呢?
举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。
在这里插入图片描述
图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。

剪枝过程
1.已经选择的元素个数:path.size();
2.所需需要的元素个数为: k - path.size();
3.列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())
4.在集合n中至多能从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历

剪枝精髓:for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够 题目要求的k个元素了,就没有必要搜索了。

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        result = []  # 存放结果集
        self.backtracking(n, k, 1, [], result)
        return result
    def backtracking(self, n, k, startIndex, path, result):
        if len(path) == k:
            result.append(path[:])
            return
        for i in range(startIndex, n - (k - len(path)) + 2):  # 优化的地方
            path.append(i)  # 处理节点
            self.backtracking(n, k, i + 1, path, result)
            path.pop()  # 回溯,撤销处理的节点

216 组合总和Ⅲ

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

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

示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]

本题k相当于树的深度,9(因为整个集合就是9个数)就是树的宽度。
在这里插入图片描述

确定参数
和77组合一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。
targetSum(int)目标和,也就是题目中的n。
k(int)就是题目中要求k个数的集合。
sum(int)为已经收集的元素的总和,也就是path里元素的总和。
startIndex(int)为下一层for循环搜索的起始位置。

确定终止条件
在上面已经说了,k其实就已经限制树的深度,因为就取k个元素,树再往下深了没有意义。
所以如果path.size() 和 k相等了,就终止。
如果此时path里收集到的元素和(sum) 和targetSum(就是题目描述的n)相同了,就用result收集当前的结果。

确定单层搜索逻辑
本题和77组合区别之一就是集合固定的就是9个数[1,…,9],所以for循环固定i<=9
处理过程就是 path收集每次选取的元素,相当于树型结构里的边,sum来统计path里元素的总和。
在这里插入图片描述

剪枝

在这里插入图片描述

代码如下(Python):

from typing import *

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        res = []
        self.backtracking(n, k, 0, 1, [], res)
        return res

    def backtracking(self, tar_sum, k, cur_sum, start_idx, path, res):
        if cur_sum > tar_sum:  # 剪枝操作
            return  # 如果path的长度等于k但currentSum不等于targetSum,则直接返回
        if len(path) == k:
            if cur_sum == tar_sum:
                res.append(path[:])
            return

        for i in range(start_idx, 9 - (k - len(path)) + 2):  # 剪枝
            cur_sum += i  # 处理
            path.append(i)  # 处理
            self.backtracking(tar_sum, k, cur_sum, i + 1, path, res)  # 注意i+1调整startIndex
            cur_sum -= i  # 回溯(别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!)
            path.pop()  # 回溯

17 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

示例 2:
输入:digits = “”
输出:[ ]

示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]

在这里插入图片描述
遍历的深度就是输入"23"的长度,宽度就是每个数字对应字符串的长度
而叶子节点就是我们要收集的结果,输出[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]

确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。
再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。
注意这个index可不是 77组合和216组合总和Ⅲ中的startIndex了。
因为本题是多个集合求组合,这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。

确定终止条件
例如输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。
那么终止条件就是如果index 等于 输入的数字个数(digits.size)了(本来index就是用来遍历digits的)。
然后收集结果,结束本层递归。

确定单层遍历逻辑
首先要取index指向的数字,并找到对应的字符集(手机键盘的字符集)。
然后for循环来处理这个字符集

from typing import List

class Solution:
    def __init__(self):
        self.letterMap = [
            "",  # 0
            "",  # 1
            "abc",  # 2
            "def",  # 3
            "ghi",  # 4
            "jkl",  # 5
            "mno",  # 6
            "pqrs",  # 7
            "tuv",  # 8
            "wxyz"  # 9
        ]
        self.res = []
        self.s = ""

    def letterCombinations(self, digits: str) -> List[str]:
        if len(digits) == 0:  # 数字为空直接返回空res
            return self.res
        self.backtracking(digits, 0)
        return self.res

    def backtracking(self, digits, index):
        if index == len(digits):  # 索引等于数字个数
            self.res.append(self.s)
            return

        digit = int(digits[index])  # 将索引处的数字转为整数
        letters = self.letterMap[digit]  # 获取对应的字符集
        for i in range(len(letters)):
            self.s += letters[i]  # 处理字符
            self.backtracking(digits, index + 1)  # 递归调用,注意索引加1,处理下一个数字
            self.s = self.s[:-1]  # 回溯,删除最后添加的元素

S = Solution()
print(S.letterCombinations("23"))

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

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

相关文章

weiyang**3.控制台01

1. 搭建单群组FISCO BCOS联盟链 使用开发部署工具 build_chain.sh脚本在本地搭建一条4 节点的FISCO BCOS链&#xff0c;以Ubuntu 22.04 64bit系统为例操作。 1.1 安装依赖 sudo apt install -y openssl curl 1.2 创建操作目录, 下载安装脚本 ## 创建操作目录 cd ~ &&a…

YOLO系列笔记(十八)—— YOLOv1和YOLOv2总结与对比

YOLOv1和的v2总结与对比 YOLOv1主要思想算法架构主干网络损失函数 训练过程网络预测主要成果优化方向 YOLOv2算法架构主干网络损失函数 相较之前的优化添加Batch Normalization引入锚框&#xff08;Anchor Boxes&#xff09;机制引入DarkNet-9多尺度输入训练 网络训练第一阶段&…

单片机学习(16)--直流电机驱动

直流电机驱动 15.1直流电机驱动基础知识1.直流电机介绍2.电机驱动电路3.PWM介绍 15.2LED呼吸灯和直流电机调速1.LED呼吸灯代码2.直流电机调速&#xff08;1&#xff09;产生PWM的方法&#xff08;2&#xff09;工程目录&#xff08;3&#xff09;main.c函数 15.1直流电机驱动基…

kaggel-汽车价格预测项目

1.读取数据&#xff0c;查看数据基本概况 import pandas as pd datapd.read_csv(r./car_price_prediction.csv)#查看前5行数据 print(data.head(5))output:ID Price Levy ... Wheel Color Airbags 0 45654403 13328 1399 ... Left wheel Silve…

css 滚动词云

css javascript 实现滚动词云效果 // 163css.js var radius 120; var dtr Math.PI / 180; var d 300; var mcList []; var active false; var lasta 1; var lastb 1; var distr true; var tspeed 10; var size 250; var mouseX 0; var mouseY 0; var howElliptic…

宝塔安装rabbitMQ实战

服务器环境说明 阿里云服务器、宝塔、centos7 一、下载erlang 原因&#xff1a;RabbitMQ服务端代码是使用并发式语言Erlang编写的&#xff0c;安装Rabbit MQ的前提是安装Erlang。 下载地址&#xff1a;http://www.erlang.org/downloads 下载对应的版本&…

进程间通信简介-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

进程间通信简介 进程间通信简介 进程间进程简称IPC(interprocess communication)&#xff0c;进程间通信就是在不同进程之间传递信息或交换信息 进程间通信的目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的…

互联网算法备案 | 填报指南

一、填报入口 登陆互联网信息服务算法备案系统&#xff08;以下简称备案系统&#xff09;进行填报&#xff0c;网址为https://beian.cac.gov.cn。系统首页如图1所示。 图1备案系统首页&#xff08;示意图&#xff09; 二、填报流程 填报人员需首先注册并登陆备案系统&#x…

用Roofline模型去分析pytorch和Triton算子

用Roofline模型去分析pytorch和Triton算子 1.参考链接2.测试环境3.安装相关依赖4.锁频5.获取理论算力6.创建测试脚本7.运行测试程序生成Roofline图8.NVIDIA Nsight Compute生成Roofline9.效果图A.nn.LinearB.Triton实现 本文演示了如何用Roofline模型去分析pytorch和Triton算子…

探秘 NTHU-DDD:疲劳与哈欠背后的驾驶安全密码(目标检测)

亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 一、引言…

【最新鸿蒙应用开发】——用户信息封装

用户管理工具封装 1. 为什么要封装 在进行如下登录功能时&#xff0c; 通常需要将一些用户信息以及token进行持久化保存&#xff0c;以方便下次进行数据请求时携带这些用户信息来进行访问后端数据。下面分享一下鸿蒙当中实用的持久化封装操作。 2. 步骤 封装用户信息管理工具…

Django 配置静态文件

1&#xff0c;DebugTrue 调试模式 Test/Test/settings.py DEBUG True...STATICFILES_DIRS [os.path.join(BASE_DIR, static),] STATIC_URL /static/ 1.1 创建静态文件 Test/static/6/images/Sni1.png 1.2 添加视图函数 Test/app6/views.py from django.shortcuts impor…

【D3.js in Action 3 精译】1.2.2 可缩放矢量图形(二)

当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知 1.2.1 HTML 与 DOM1.2.2 SVG - 可缩放矢量图形 ✔️ 第一部分【第二部分】✔️第三部分&#xff08;精译中 ⏳&#xff09; 1.2.3 Canvas 与 WebGL&#x…

互联网大厂核心知识总结PDF资料

我们要敢于追求卓越&#xff0c;也能承认自己平庸&#xff0c;不要低估3&#xff0c;5&#xff0c;10年沉淀的威力 hi 大家好&#xff0c;我是大师兄&#xff0c;大厂工作特点是需要多方面的知识和技能。这种学习和积累一般人需要一段的时间&#xff0c;不太可能一蹴而就&…

获取 url 地址栏 ? 后面的查询字符串,并以键值对形式放到对象里面

写在前面 在前端面试当中&#xff0c;关于 url 相关的问题很常见&#xff0c;而对于 url 请求参数的问题也很常见&#xff0c;大部分以笔试题常见&#xff0c;今天就根据这道面试题一起来看一下。 问题 获取 url 地址栏?后面的查询字符串&#xff0c;并以键值对形式放到对象…

【图解大数据技术】Hadoop、HDFS、MapReduce、Yarn

【图解大数据技术】Hadoop、HDFS、MapReduce、Yarn HadoopHDFSHDFS架构写文件流程读文件流程 MapReduceMapReduce简介MapReduce整体流程 Yarn Hadoop Hadoop是Apache开源的分布式大数据存储与计算框架&#xff0c;由HDFS、MapReduce、Yarn三部分组成。广义上的Hadoop其实是指H…

Ubuntu网络管理命令:netstat

安装Ubuntu桌面系统&#xff08;虚拟机&#xff09;_虚拟机安装ubuntu桌面版-CSDN博客 顾名思义&#xff0c;netstat命令不是用来配置网络的&#xff0c;而是用来查看各种网络信息的&#xff0c;包括网络连接、路由表以及网络接口的各种统计数据等。 netstat命令的基本语法如…

Sora DiT图解【文生视频】

“在古老的迪萨罗斯大陆&#xff0c;曾经住着一位传奇人物&#xff0c;名叫索拉&#xff08;Sora&#xff09;。这个传奇人物体现了无限潜力的本质&#xff0c;包括天空的浩瀚和壮丽。 当它飞得很高&#xff0c;彩虹色的翅膀横跨广阔的空间&#xff0c;光线从它引人注目的身体…

QTreeView第一列自适应

通过setStretchLastSection(bool stretch)可以设置最后一列自适应,对于QTreeView,stretch默认为true。但有时候我们需要设置第一列自适应,比如文件浏览器,共有名称、大小和修改日期三列,大小和日期的宽度几乎是固定的,但名称却可长可短,此时我们希望在窗口大小变化时,第…