【LeetCode回溯算法#01】图解组合问题
组合问题
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
思路
如果像题目给的例子那样,k是一个较小的值(比如2),那么一个很直接的想法是使用两层for循环去遍历数组即可
vector<vector<int>> res;
vector<int> temp;
for(int i = 0; i < n; ++i){
for(int j = i; j < n; ++j){
temp.push_back(i);
temp.push_back(j);
res.push_back(temp);
temp.clear();
}
}
return res;
那如果k要是100呢?写100层for循环?
显然,使用for循环进行暴力遍历的话,遍历的深度是有限的
既然涉及到了深度,那么递归和树结构就很适合来解决此类问题了
首先,递归可以在设置的条件范围内搜索到足够的深度,我们实际上只用在递归的最后一层进行横向遍历(即for循环),其余的则在回溯过程中处理
其次,我们可以将整个搜索过程想象成一个树结构
当我们通过递归到达某个分支的"叶子结点"后,这条路径上保存的节点值就是一个组合的结果
下面贴一下卡哥的图和我经过理解补充的图,可以对比来看方便理解


可以看到,每层递归中都有一个for循环,目的是用于处理本层递归的横向遍历过程
当第一层递归被调用时,其中的for循环会依次处理[1,2,3,4],取1(将1保存)
此时又触发一层递归,第二层递归会依次处理[2,3,4],取2(将2保存)
此时,再次触发递归,到达第三层,在第三层递归中,我们判定用于保存结果的数组(目前为[1,2])的大小已经等于k值
因此,在第三层递归中触发了终止条件,递归结束
回溯,到达第二层递归,此时将结果数组中的2弹出
回溯,到达第一层递归,此时将结果数组中的1弹出
第一层递归中的for继续遍历到下一个数(即2),然后重复上述递归过程
当然,在第二层递归中,每次起始的遍历位置是不同的,后面代码里会解释
代码分析
写递归嘛,那还是遵循递归的步骤,但这里其实可以改叫回溯三部曲了
1、确定递归函数的返回值和参数
本质上我们是使用递归去操作/遍历一个数组,因此不需要返回值
输入参数那肯定是遍历区间n和组合大小k了
经过上面的分析得知,我们还需要一个数组来保存每层递归取到的结果
这里将该数组命名为path,其记录的是我们从第一层递归到最里层递归这条路径上遇到并保存的值
还需要一个数组res来保存所有的path
并且,在第二层递归中,为了防止组合中出现重复值([1,2]和[2,1]被视为相同结果),我们需要不断调整遍历的范围
因此输入参数中还需要一个变量给出遍历的起始位置
class Solution {
public:
//定义数组
vector<int> path;
vector<vector<int>> res;//保存最终结果
void backtracking(int n, int k, int beginIndex){
}
vector<vector<int>> combine(int n, int k) {
}
};
2、确定终止条件
还是通过前面的讨论得知,当我们到达最里层递归时,需要终止。即到达我们想象的二叉树结果的叶子节点
如何判断"到达叶子节点"这件事呢?
其实可以通过路径数组path的大小来判断,如果path的大小等于k,说明递归的深度已经到头了,此时应该结束递归
class Solution {
public:
//定义数组
vector<int> path;
vector<vector<int>> res;//保存最终结果
void backtracking(int n, int k, int beginIndex){
//确定终止条件
if(path.size() == k){
//保存path到res
res.push_back(path);
return;//结束递归
}
}
vector<vector<int>> combine(int n, int k) {
}
};
3、确定单层处理逻辑
这一部分需要去规定在本层递归中应该如何遍历数组
还是结合前面的分析:我们在一层递归中实际需要做的是使用for对当前以beginIndex为起始,以n为结束(即[beginIndex, n])的数组进行遍历
那就写for循环呗
class Solution {
public:
//定义数组
vector<int> path;
vector<vector<int>> res;//保存最终结果
void backtracking(int n, int k, int beginIndex){
//确定终止条件
if(path.size() == k){
//保存path到res
res.push_back(path);
return;//结束递归
}
//确定单层处理逻辑
//遍历本层递归的数组,范围是[beginIndex, n]
for(int i = beginIndex; i < n; ++i){
//添加当前路径节点值到path
path.push_back(i);
//进入下一层递归
backtracking(int n, int k, i + 1);//跳过当前值
//编写回溯处理逻辑
//回溯时删掉当前层保存的值
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
}
};
打完收工
完整代码
在主函数中,把backtracking调用并给出beginIndex的初值即可
class Solution {
public:
//定义数组
vector<int> path;
vector<vector<int>> res;//保存最终结果
void backtracking(int n, int k, int beginIndex){
//确定终止条件
if(path.size() == k){
//保存path到res
res.push_back(path);
return;//结束递归
}
//确定单层处理逻辑
//遍历本层递归的数组,范围是[beginIndex, n]
for(int i = beginIndex; i <= n; ++i){
//添加当前路径节点值到path
path.push_back(i);
//进入下一层递归
backtracking(n, k, i + 1);//跳过当前值
//编写回溯处理逻辑
//回溯时删掉当前层保存的值
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
//可以先清一下两个结果数组
//res.clear();
//path.clear();
backtracking(n, k, 1);//调用递归去操作数组
return res;
}
};
注意点
遍历本层递归时,i是要 小于等于 n的,并且beginIndex的初始值应该是1
这里需要厘清一个关系
本题中并不是真的有一个数组等着我们去遍历(分析时写[1,2,3,4]也只是便于理解),只是给定的一个范围,在该范围中找出所有两个元素的组合
因此要在[1,4]范围中找出所有两个元素的组合,就真的要从1开始,并且也要包含4
(不要错误的认为从1开始就漏了[1,2,3,4]中的1,结束位置是4就超出数组范围。因为根本就没有数组)
剪枝优化
TBD
【LeetCode回溯算法#01】图解组合问题的更多相关文章
- 【LeetCode回溯算法#07】子集问题I+II,巩固解题模板并详解回溯算法中的去重问题
子集 力扣题目链接 给你一个整数数组 nums ,数组中的元素 互不相同 .返回该数组所有可能的子集(幂集). 解集 不能 包含重复的子集.你可以按 任意顺序 返回解集. 示例 1: 输入:nums ...
- 【LeetCode回溯算法#10】图解N皇后问题(即回溯算法在二维数组中的应用)
N皇后 力扣题目链接(opens new window) n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 给你一个整数 n ,返回所有不同的 n 皇 ...
- 【LeetCode回溯算法#extra01】集合划分问题【火柴拼正方形、划分k个相等子集、公平发饼干】
火柴拼正方形 https://leetcode.cn/problems/matchsticks-to-square/ 你将得到一个整数数组 matchsticks ,其中 matchsticks[i] ...
- leetcode回溯算法--基础难度
都是直接dfs,算是巩固一下 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合. 给出数字到字母的映射如下(与电话按键相同).注意 1 不对应任何字母. 思路 一直 ...
- 【LeetCode回溯算法#06】复原IP地址详解(练习如何处理边界条件,判断IP合法性)
复原IP地址 力扣题目链接(opens new window) 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 ...
- 【LeetCode回溯算法#08】递增子序列,巩固回溯算法中的去重问题
递增子序列 力扣题目链接(opens new window) 给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2. 示例 1: 输入:nums = [4,6,7,7] ...
- LeetCode初级算法--链表01:反转链表
LeetCode初级算法--链表01:反转链表 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/ ...
- LeetCode刷题191203 --回溯算法
虽然不是每天都刷,但还是不想改标题,(手动狗头 题目及解法来自于力扣(LeetCode),传送门. 算法(78): 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明: ...
- LeetCode通关:连刷十四题,回溯算法完全攻略
刷题路线:https://github.com/youngyangyang04/leetcode-master 大家好,我是被算法题虐到泪流满面的老三,只能靠发发文章给自己打气! 这一节,我们来看看回 ...
- LeetCode:回溯算法
回溯算法 这部分主要是学习了 labuladong 公众号中对于回溯算法的讲解 刷了些 leetcode 题,在此做一些记录,不然没几天就忘光光了 总结 概述 回溯是 DFS 中的一种技巧.回溯法采用 ...
随机推荐
- lldb3.9.0 安装攻略
Study From https://github.com/dotnet/diagnostics/blob/master/documentation/lldb/centos7/build-instal ...
- Docker容器基础入门认知-Cgroup
在上一篇说完 namespace 给容器技术提供了隔离之后,我们在介绍一下容器的"限制"问题 也许你会好奇,我们不是已经通过 Linux Namespace 给容器创建了一个容器了 ...
- 【K哥爬虫普法】12亿公民信息泄露,仅判3年,个人信息是否为爬虫“禁区”?
我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K 哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识, ...
- 全套解决方案:中文NLP训练框架,支持大模型训练和文本生成,快速上手,海量训练数据!
全套解决方案:基于pytorch.transformers的中文NLP训练框架,支持大模型训练和文本生成,快速上手,海量训练数据! 1.简介 目标:基于pytorch.transformers做中文领 ...
- python读取json格式文件大量数据,以及python字典和列表嵌套用法详解
1.Python读取JSON报错:JSONDecodeError:Extra data:line 2 column 1 错误原因: JSON数据中数据存在多行,在读取数据时,不能够单单用open(), ...
- C/C++ 反汇编:针对加减乘除的还原
算术运算通常是指,加减乘除四则运算,而计算机中的四则运算与数学中的有所不同,同样是实现算术运算,高级语言与汇编语言的实现思路完全不同,往往一个简单的减法运算,都要几条指令的配合才能得出计算结果,而为了 ...
- C# 实现对网站Get与Post请求
C# 是一种面向对象的编程语言,提供了强大的Web请求库和API来执行 HTTP GET 和 POST 请求.在C#中,我们可以使用 System.Net 命名空间下的 WebRequest 和 We ...
- Linux 配置Quota磁盘配额
由于Linux是一个多用户管理的操作系统,而Linux默认情况下并不限制每个用户使用磁盘空间的大小,假如某个用户疏忽或者恶意占满磁盘空间,将导致系统磁盘无法写入甚至崩溃,为了保证系统磁盘的有足够的剩余 ...
- HarmonyOS 实战小项目开发(二)
HarmonyOS 实战小项目开发(二) 日常逼逼叨 在上期实战项目一中,已经对于练手项目的背景,后端搭建等做了一定的简述,那么本期将结合HarmonyOS 页面搭建个人性格测试的移动端.如有一些错误 ...
- (python)每日代码||2024.1.27||类方法与实例方法
class test(): aaa = 111 bbb = 222 ccc = 333 @classmethod def cm(cls): cls.aaa="***" def im ...