【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 中的一种技巧.回溯法采用 ...
随机推荐
- ARM 平台Docker运行RabbitMQ 以及迁移的简单办法
公司网络很垃圾. 可以使用vps 进行下载和打包 放到 公司的机器上面进行使用. 1. 搜索有没有可用的镜像. [root@JNXLH ~]# docker search rabbitmq |gre ...
- 使用Grafana 监控 SQLSERVER数据库
使用Grafana 监控 SQLSERVER数据库 1.获取镜像信息以及启动镜像 docker pull awaragi/prometheus-mssql-exporter docker run -e ...
- 2024年最新的Python操控微信教程
自从微信禁止网页版登陆之后,itchat 库实现的功能也就都不能用了,那现在 Python 还能操作微信吗?答案是:可以! 在Github上有一个项目叫<WeChatPYAPI>可以使用 ...
- 模块化Common.js与ES6
为什么要模块化开发 1. 依赖关系(a文件依赖b文件中的方法,b文件必须在a文件之前引入) 2. 命名问题 (多个文件变量名,方法名相同会出现覆盖) 3. 代码组织(后期不好维护) 模块化规范有 1. ...
- 探索 GO 项目依赖包管理与Go Module常规操作
探索 GO 项目依赖包管理与Go Module常规操作 目录 探索 GO 项目依赖包管理与Go Module常规操作 一.Go 构建模式的演变 1.1 GOPATH (初版) 1.1.1 go get ...
- 树状数组(区间修改&&区间查询)
#include<bits/stdc++.h> #define int long long using namespace std; int n,m,x,x1,y,z; int a[100 ...
- HarmonyOS实战[二]—超级详细的原子化服务体验[可编辑的卡片交互]快来尝试吧
相关文章: HarmonyOS实战[一]--原理概念介绍安装:基础篇 [本文正在参与"有奖征文|HarmoneyOS征文大赛"活动] 1.创建HarmonyOS应用 选择Java程 ...
- 8.3 Windows驱动开发:内核遍历文件或目录
在笔者前一篇文章<内核文件读写系列函数>简单的介绍了内核中如何对文件进行基本的读写操作,本章我们将实现内核下遍历文件或目录这一功能,该功能的实现需要依赖于ZwQueryDirectoryF ...
- AIX6.1系统NTP同步配置
前言 当AIX系统的本地时间与时间服务器授出的标准时间误差大于±1000秒时.xntpd服务将无法同步时间并变得无法正常工作,请进行ntp配置前,先修改AIX系统的本地时间,尽量和时间服务器的标准 ...
- Dash 2.15版本新特性介绍
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/dash-master 大家好我是费老师,Dash不久前发布了其2.15.0版本,新增了一些实用的特性 ...