【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 中的一种技巧.回溯法采用 ...
随机推荐
- adb基础指令
1.查看连接的设备 adb.exe devices/adb devices device表示连接成功,offline表示设备未连接或未响应 2.进入命令模式,指定哪台设备 adb -s 序列号 ...
- javaScript随机图片
<script type="text/javascript"> //<!CDATA[ var pic = []; pic[0] = "链接"; ...
- C# 求两个时间的差值
商品保质期 //dateStart:系统时间: dateEnd :物品到期日期 DateTime dateStart = DateTime.Now.Date;//2021/7/8 DateTime d ...
- 提升vscode的搜索速度
在全局搜索速度上vscode比pycharm要慢不少,尤其是对于我们这种近二十年历史的项目代码来说特别明显,所以这里记录一下我是如何加快vscode的搜索速度的. 官方的搜索建议 https://co ...
- mac接入两根网线
mac机接了USB扩展槽之后,扩展槽可以接入一根网线,机器自带一个网线口,这样就可以同时接入两根网线,为什么要这样做? 因为我所在的公司是分内外网的,研发的机器是内网环境,而打包机有时候需要联网,那么 ...
- 【学习日志】Java基本数据类型的自动装箱和拆箱
// 测试代码 public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Intege ...
- 文心千帆:PPT 制作、数字人主播一键开播等数十种应用场景惊艳到我了,下面给出简介和使用指南,快去使用起来吧
文心千帆:PPT 制作.数字人主播一键开播等数十种应用场景惊艳到我了,下面给出简介和使用指南,快去使用起来吧 文心千帆大模型平台是面向企业开发者的一站式大模型开发及服务运行平台.文心千帆不仅提供了包括 ...
- MySQL【三】---数据库查询详细教程{分页、连接查询、自关联、子查询、数据库设计规范}
1.分页 limit start count limit限制查询出来的数据个数,limit在语句最后 查找两个女性 select * from student where gender=1 limit ...
- 2.1 C/C++ 使用数组与指针
C/C++语言是一种通用的编程语言,具有高效.灵活和可移植等特点.C语言主要用于系统编程,如操作系统.编译器.数据库等:C语言是C语言的扩展,增加了面向对象编程的特性,适用于大型软件系统.图形用户界面 ...
- 优化算法之梯度下降|Matlab实现梯度下降算法
题目要求: 使用Matab实现梯度下降法 对于函数: min f ( x ) = 2 x 1 2 + 4 x 2 2 − 6 x 1 − 2 x 1 x 2 \min f(x)=2 x_{1}^{ ...