回溯算法经典问题总结(.NET版)
回溯算法
回溯法其实也是一种递归,本质上就是穷举,然后筛选出符合规则的数据。为了使回溯更加高效,我们根据规则要求,在穷举过程中加上条件限制(也就是剪枝)。
我们什么场景下应该想到使用回溯法呢?
如何画图去分析问题?
如何使用代码实现呢?
如何去优化程序?
回溯算法经典问题(使用场景)
- 组合问题:N个数⾥⾯按给定规则找出k个数的集合
- 切割问题:⼀个字符串按⼀定规则有⼏种切割⽅式
- ⼦集问题:⼀个N个数的集合⾥有多少符合条件的⼦集
- 排列问题:N个数按⼀定规则全排列,有⼏种排列⽅式
- 棋盘问题:N皇后,解数独等等
组合是不强调元素顺序的,排列是强调元素顺序
画图分析回溯
面对回溯问题,我们第一想法就是画图!所有回溯法的问题都可以抽象为树形结构!
回溯法⼀般是在集合中递归搜索,集合的⼤⼩构成了树的宽度,递归的深度构成的树的深度。
回溯法模板
public void Backtracking(参数)
{
if(满足终止条件)
{
存放结果
return;
}
for(遍历本层集合中的元素)
{
处理结点;
dfs(参数);//递归
撤销处理该结点; //回溯
}
}
看下面几个例子加深理解。
组合问题
77.组合
题目:给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
第一步:画图
我们已经知道组合是不考虑顺序的,[1,2],[2,1] 是一样的。所以我们在取数的时候,只需要取当前下标及后边下标即可。横向就是给定的n,纵向就是给定的k。按照示例n=4,k=2画出树状图如下:
第二步:解题思路及模板的使用
//1.确定返回结果
IList<IList<int>> res = new List<IList<int>>();//存放符合条件的结果集合
List<int> path = new List<int>();//存放符合条件的结果
//2.确定入参
// * 题目给定的集合需要进入函数(此处是n,也可以是数组等集合)
// * 需要知道终结条件,本题为k
// * 题目要求返回组合,不考虑顺序,所以需要一个变量来记录当前选取n位置
//3.确定回溯方法
// startIndex用来记录本层递归的中,集合从哪里开始遍历
public void Backtracking(int n,int k,int startIndex)
{
//4.终止条件
if(path.Count==k)
{
//存放结果
res.Add(new List<int>(path));
return;
}
for(int i=startIndex;i<=n;i++)
{
path.Add(i);//处理节点
Backtracking(n,i+1);//递归
path.Remove(path.Count-1);//撤销处理该结点; 回溯
}
}
第三步:优化剪枝
按照上边,收集叶子节点就可以写出程序了。我们如何优化程序,如何实现剪枝呢?比如n=4,k=4,按照上图所示,会进入很多次不必要的分支。我们应该剪掉。当剩余节点不能够满足条件时,就不必继续进行了。如下图所示:
最终:完整代码
IList<IList<int>> res = new List<IList<int>>();
IList<int> path = new List<int>();
public IList<IList<int>> Combine(int n, int k)
{
BackTracking(n, k, 1);
return res;
}
public void BackTracking(int n, int k, int startIndex)
{
if (path.Count == k)
{
res.Add(new List<int>(path));
return;
}
// n - (k - path.Count) + 1
//剪枝。如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
for (int i = startIndex; i <= n - (k - path.Count) + 1; i++)
{
path.Add(i);
BackTracking(n, k, i + 1);
path.RemoveAt(path.Count - 1);
}
}
40.组合总和 II
题目:给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
第一步:画图
不包含重复的组合
例如当 {candidates} = [2, 2],{target} = 2时,上述算法会将列表 [2][2] 放入答案两次。
我们可以用一个Hash,咱.NET中使用Dictionary,将每次出现的元素和频率记录下来。不过更方便的是我们将candidates排序,查看是否和上一个元素相同。
第二步:套模板
private IList<IList<int>> result = new List<IList<int>>();//结果集
private List<int> path = new List<int>();//结果
int sum;
/// <summary>
///
/// </summary>
/// <param name="candidates">元素数组</param>
/// <param name="startIndex">开始下标</param>
/// <param name="isUsed">记录是否使用</param>
/// <param name="target">条件</param>
public void BackTracking(int[] candidates, int startIndex, bool[] isUsed, int target)
{
if (sum == target) //终止条件
{
result.Add(new List<int>(path));//收集结果
return;
}
for (int i = startIndex; i < candidates.Length && candidates[i] + sum <= target; i++)
{
//出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
if (i > 0 && candidates[i] == candidates[i - 1] && !isUsed[i - 1])
{
continue;
}
isUsed[i] = true;
sum += candidates[i];
path.Add(candidates[i]);
//每个节点仅能选择一次,所以从下一位开始
BackTracking(candidates, i + 1, isUsed, target);
int temp = path[path.Count - 1];
path.Remove(path[path.Count - 1]);
isUsed[i] = false;
sum -= temp;
}
}
第三步:剪枝优化
- 如果选取的元素,和已经大于目标值就没必要继续进行了。
candidates[i] + sum <= target
- 如果同层相同元素已经使用过,可以剪掉。不理解可以Debug跟一下,或者看着图想一下。
if (i > 0 && candidates[i] == candidates[i - 1] && !isUsed[i - 1])
最终:完整代码
private IList<IList<int>> result=new List<IList<int>>();
private IList<int> path=new List<int>();
int sum;
public IList<IList<int>> CombinationSum2(int[] candidates, int target)
{
Array.Sort(candidates);
bool[] used = new bool[candidates.Length];
BackTracking(candidates, 0, used, target);
return result;
}
public void BackTracking(int[] candidates, int index, bool[] used, int target)
{
if (sum == target)
{
result.Add(new List<int>(path));
return;
}
for (int i = index; i < candidates.Length && candidates[i]+sum<=target; i++)
{
//出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1])
{
continue;
}
used[i] = true;
sum += candidates[i];
path.Add(candidates[i]);
//每个节点仅能选择一次,所以从下一位开始
BackTracking(candidates, i+1, used, target);
int temp = path[path.Count - 1];
path.Remove(path[path.Count-1]);
used[i] = false;
sum -= temp;
}
}
216.组合总和 III
题目:找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
第一步:画图
其实这道题和第一题差不多,就是收集结果时加了一个和的限制。
第二步:套模板
IList<IList<int>> res = new List<IList<int>>();
IList<int> path = new List<int>();
/// <summary>
///
/// </summary>
/// <param name="targetSum">给定目标和</param>
/// <param name="k">要求元素个数</param>
/// <param name="startIndex">开始下标</param>
/// <param name="sum">当前已选取元素和</param>
public void BackTracking(int targetSum, int k, int startIndex, int sum)
{
// 减枝
if (sum > targetSum)
{
return;
}
if (path.Count == k)
{
if (targetSum == sum)
res.Add(new List<int>(path));
return;
}
// 减枝 9 - (k - path.size()) + 1
for (int i = startIndex; i <= 9 - (k - path.Count) + 1; i++)
{
path.Add(i);
sum += i;
BackTracking(targetSum, k, i + 1, sum);
//回溯
path.RemoveAt(path.Count - 1);
//回溯
sum -= i;
}
}
第三步:剪枝优化
- 选取元素小于等于目标和
- 剩余元素个数满足条件k
最终:完整代码
IList<IList<int>> res = new List<IList<int>>();
IList<int> path = new List<int>();
public IList<IList<int>> CombinationSum3(int k, int n)
{
BackTracking(n, k, 1, 0);
return res;
}
public void BackTracking(int targetSum, int k, int startIndex, int sum)
{
// 减枝
if (sum > targetSum)
{
return;
}
if (path.Count == k)
{
if (targetSum == sum)
res.Add(new List<int>(path));
return;
}
// 减枝 9 - (k - path.size()) + 1
for (int i = startIndex; i <= 9 - (k - path.Count) + 1; i++)
{
path.Add(i);
sum += i;
BackTracking(targetSum, k, i + 1, sum);
//回溯
path.RemoveAt(path.Count - 1);
//回溯
sum -= i;
}
}
注意点
组合是无序的,所以需要strartIndex来控制选择范围,查找当前结果时,不可以再次使用。当然如果选取元素不是同一集合,就不要用startIndex了。比如17. 电话号码的字母组合
题目给的元素集合,是否存在重复元素,如果存在,需要对同层相同已使用元素进行剪枝
结果长度是否有要求?如果有要求判断剩余元素是否满足长度要求。
切割问题
⼦集问题
排列问题
棋盘问题
类似问题
洪水算法 flood fill
第一步:画图
第二步:套模板
第三步:剪枝优化
最终:完整代码
回溯算法经典问题总结(.NET版)的更多相关文章
- 《转载》常用算法经典代码(C++版)
转自:http://blog.renren.com/blog/311453043/736944237 一.快速排序 void qsort(int x,int y) //待排序的数据存放在a[1]..a ...
- 剑指Offer——回溯算法解迷宫问题(java版)
剑指Offer--回溯算法解迷宫问题(java版) 以一个M×N的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍.设计程序,对任意设定的迷宫,求出从入口到出口的所有通路. 下面我们来详细讲一 ...
- 3、回溯算法解题套路框架——Go语言版
前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...
- 回溯算法之n皇后问题
今天在看深度优先算法的时候,联想到DFS本质不就是一个递归回溯算法问题,只不过它是应用在图论上的.OK,写下这篇博文也是为了回顾一下回溯算法设计吧. 学习回溯算法问题,最为经典的问题我想应该就是八皇后 ...
- 【LeetCode-面试算法经典-Java实现】【079-Word Search(单词搜索)】
[079-Word Search(单词搜索)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a 2D board and a word, find if ...
- 【LeetCode-面试算法经典-Java实现】【129-Sum Root to Leaf Numbers(全部根到叶子结点组组成的数字相加)】
[129-Sum Root to Leaf Numbers(全部根到叶子结点组组成的数字相加)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a bina ...
- 计科1111-1114班第一次实验作业(NPC问题——回溯算法、聚类分析)
实验课安排 地点: 科技楼423 时间: 计科3-4班---15周周一上午.周二下午 计科1-2班---15周周一下午.周二晚上(晚上时间从18:30-21:10) 请各班学委在实验课前飞信通知大家 ...
- 算法刷题--回溯算法与N皇后
所谓回溯算法,在笔者看来就是一种直接地思想----假设需要很多步操作才能求得最终的解,每一步操作又有很多种选择,那么我们就直接选择其中一种并依次深入下去.直到求得最终的结果,或是遇到明细的错误,回溯到 ...
- C#数据结构与算法系列(十四):递归——八皇后问题(回溯算法)
1.介绍 八皇后问题,是一个古老而著名的问题,是回溯算法的经典案例,该问题是国际西洋棋棋手马克斯.贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即 任意两个皇后都不能处 ...
- 【数据结构与算法】多种语言(VB、C、C#、JavaScript)系列数据结构算法经典案例教程合集目录
目录 1. 专栏简介 2. 专栏地址 3. 专栏目录 1. 专栏简介 2. 专栏地址 「 刘一哥与GIS的故事 」之<数据结构与算法> 3. 专栏目录 [经典回放]多种语言系列数据结构算法 ...
随机推荐
- Springboot_maven多环境配置
开发过程中总是需要多环境配置,而Spring自带的方式不是那么优秀,可以利用maven来帮助做到 可以再pom.xml中配置profiles来做到 打包命令: mvn clean package -P ...
- MySQL 不同隔离级别,都使用了什么锁?
大家好,我是树哥. 在上篇文章,我们聊了「MySQL 啥时候会用表锁,啥时候用行锁」这个问题.在文章中,我们还留了一个问题,即:如果查询或更新时的数据特别多,是否从行锁会升级为表锁?此外,还有朋友留言 ...
- 输入法词库解析(五)极点码表.mb
详细代码:https://github.com/cxcn/dtool 前言 mb 是极点五笔的码表格式. 解析 偏移量 描述 0x00 版本信息 0x1B 码表介绍 0x11F 所用到的按键数 0x1 ...
- jenkins修改默认的workspace工作目录
1.首先,找到Jenkins安装根目录,寻找config.xml文件,在config.xml文件内,查找 workspaceDir 关键字,将你的自定义 工作空间根目录 地址替换默认的地址 # cd ...
- STM32F0单片机基于Hal库温控智能风扇
一.项目概述 设计采用STM32F0系列单片机做主控芯片,通过DHT11采集温湿度,将温度显示在OLED 屏幕上.根据温度的不同,利用STM32对风扇进行调速,总体硬件设计如下图所示 1.效果展示 2 ...
- HM VNISEdit2.0.3修正版
HM VNISEdit,曾经是NSIS最强最佳开源免费编辑器/IDE,但2003年至今原作者已经接近20年未再更新,随着NSIS3.X版本的普及,NIS Edit不可避免的出现了大大小小的各种BUG, ...
- 关于AWS基于AMI还原实例后不能通过口令密码ssh登录的解决方法
最近笔者在工作中,通过备份的AMI,还原创建实例后,发现不能使用密码口令登录,登录时会报如下错误: [root@localhost ~]# ssh qq_5201351@13.250.125.37 W ...
- MatrixOne从入门到实践08——SSB性能测试
MatrixOne从入门到实践--SSB性能测试 SSB 星型模式基准测试是 OLAP 数据库性能测试的常用场景,通过本篇教程,您可以了解到如何在 MatrixOne 中实现 SSB 测试. 测试环境 ...
- 驱动开发:内核R3与R0内存映射拷贝
在上一篇博文<驱动开发:内核通过PEB得到进程参数>中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息,本篇文章同样需要使用进程附加功能,但这 ...
- AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)
AgileBoot是笔者在业余时间基于ruoyi改造优化的前后端全栈项目. 关于AgileBoot的详细介绍:https://www.cnblogs.com/valarchie/p/16777336. ...