【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 中的一种技巧.回溯法采用 ...
随机推荐
- 【转帖】Docker容器四种网络模式
https://blog.whsir.com/post-5268.html docker自身默认提供了四种网络模式:none.bridge.container.host.除了这四种网络模式外,还可以通 ...
- BAdI:INVOICE_UPDATE 导致MM Invoice Doc. Missing
Symptom:发票校验过程中,对应发票号生成,FI凭证也产生,但是对应RBKP,RSEG中无相应的发票. 原先这一问题SAP早给出过解释,参照note:1876234 Cause:在SD MM模块中 ...
- React类组件中事件绑定this指向的三种方式
有状态组件和无状态组件 函数组件又叫做无状态组件,类组件又叫做有状态组件. 状态又叫做数据 函数组件没有自己的状态,只负责静态页面的展示. 我们可以理解为纯ui展示.() 类组件有自己的状态,扶着更新 ...
- elementui出现展开后子菜单宽度多出1px问题
添加 就可以解决了 .el-menu { border-right-width: 0; } <template> <div class="compen-left-men&q ...
- 大数据面试题集锦-Hadoop面试题(一)
目录 1.集群的最主要瓶颈 2.Hadoop运行模式 3.Hadoop生态圈的组件并做简要描述 4.解释"hadoop"和"hadoop 生态系统"两个概念 5 ...
- 3.1 Windows驱动开发:内核远程堆分配与销毁
在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了ZwAllocateVirtualMemory这 ...
- C/C++ 病毒破坏手法总结
针对注册表恶意修改: #include <stdio.h> #include <Windows.h> // 禁用系统任务管理器 void RegTaskmanagerForbi ...
- 20.3 DLL入口函数--《Windows核心编程》
如果在执行一些与进程或者线程有关的初始化或者销毁工作的时候,需要 DllMain.如果只需要创建一个包含资源的DLL,不需要这个函数. B00L WINAPI DllMain(HINSTANCE hi ...
- 好书推荐之《JAVA编程思想》
名人推荐 真是一本透着编程思想的书. 上面的书让你从微观角度了解 Java,而这本书则可以让你从一个宏观角度了解 Java. 这本书和 Java 核心技术的厚度差不多,但这本书的信息密度比较大. 所以 ...
- NOI 2019 补全记录
D1T1 回家路线 好久之前写的,忘了具体细节,但是发现有平方项所以考虑拆项之后斜率优化. D1T2 机器人 考虑 DP. 记 \(f_{l,r,i}\) 表示 \([l,r]\) 这段区间,最大值为 ...