文章目录

  • 回溯
    • 电话号码的字母组合
      • 二进制手表
    • 组合总数
    • 全排列
      • 活字印刷
    • N皇后
      • N皇后II

回溯

回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。

当探索到某一步时,发现原先选择并不优或 达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。也可以称为剪枝点,所谓的剪枝,指的是把不会找到目标,或者不必要的路径裁剪掉。

从上面看出,回溯其实就是选优搜索法,按照深度优先搜索的方式进行搜索,再通过剪枝来避免不相关结果。

//模板
回溯()
{if(满足结束条件){将结果加入结果集中return;}for(当前选择 : 选择列表){1.完成这一步的选择2.回溯(下一步), 进行下一步的选择3.撤销选择,继续进行其他尝试}
}

电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

DFS + 回溯,如果当前组合行不通则回溯到上一步,然后继续尝试其他组合。

class Solution {
public:vector<string> numToStr = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};    //数字——字母映射void DFS(vector<string>& res, string& digits, string cur, int index){//此时数字已遍历结束if(index == digits.size()){//如果组合不为空,则加入结果集中if(!cur.empty()){res.push_back(cur);return;}}//搜索当前所有可能的组合for(auto ch : numToStr[digits[index] - '0']){cur.push_back(ch);DFS(res, digits, cur, index + 1);//回溯到上一步的状态cur.pop_back();}}vector<string> letterCombinations(string digits) {if(digits.empty()){return {};}vector<string> res;DFS(res, digits, "", 0);return res;}
};

二进制手表

二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。

每个 LED 代表一个 0 或 1,最低位在右侧。

例如,上面的二进制手表读取 “3:25”。

给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-watch

和上题思路一样,不过变为了组合二进制。

class Solution {
public:vector<int> nToTime = {1, 2, 4, 8, 1, 2, 4, 8, 16, 32};void DFS(vector<string>& res, int num, int index, pair<int, int>& time){//如果没有剩余的灯,就返回if(num == 0){if(time.first > 11 || time.second > 59){return;}string cur = (to_string(time.first) + ":");if(time.second < 10)    //小于10要补0{cur += '0';}cur += to_string(time.second);res.push_back(cur); //组合结果return;}//尝试当前每一种组合for(int i = index; i < 10; i++){//超出范围,舍弃该数据if(time.first > 11 || time.second > 59){continue;}pair<int, int> backUp = time;//前四个灯为小时if(i < 4){time.first += nToTime[i];}//后六个灯为分钟else{time.second += nToTime[i];}DFS(res, num - 1, i + 1, time); //进入下一层搜索, 下次从第i + 1栈灯开始time = backUp;  //回溯状态}}vector<string> readBinaryWatch(int num) {vector<string> res;pair<int, int> time(0, 0);DFS(res, num, 0, time);return res;}
};

组合总数

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum

和上面的思路一样,不过此时由于数据可以无限被选取,所以搜索时下一步的位置还是从当前位置i开始,不需要继续推进。(这道题目也可以当成完全背包,与动态规划题解中的零钱问题II类似)

class Solution {
public:void DFS(vector<int>& candidates, vector<vector<int>>& res, vector<int>& cur, int target, int sum, int index){//如果大于等于target,则不可再增加if(sum >= target){//如果和target相等则返回结果集if(sum == target){res.push_back(cur);}return;}//尝试当前各种结果for(int i = index; i < candidates.size(); i++){//如果当前元素比target大,则不可能成立,直接跳过if(candidates[i] > target){continue;}cur.push_back(candidates[i]);DFS(candidates, res, cur, target, sum + candidates[i], i);  //由于此时数据可以被无限选取,所以下一步的位置继续从i开始搜索cur.pop_back(); //回溯}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {if(candidates.empty()){return {};}vector<vector<int>> res;vector<int> cur;DFS(candidates, res, cur, target, 0, 0);return res;}
};

全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

照着模板就可以写出来

class Solution {
public:void DFS(vector<vector<int>>& allRet, vector<int>& curRet, vector<int>& nums, vector<bool>& visited){//满足条件则加入结果集if(curRet.size() == nums.size()){allRet.push_back(curRet);}for(int i = 0; i < nums.size(); i++){//跳过以访问数字if(visited[i] == false){continue;}//记录当前结果curRet.push_back(nums[i]);visited[i] = false;//进行下一步的搜索DFS(allRet, curRet, nums, visited);//回溯curRet.pop_back();visited[i] = true;}}vector<vector<int>> permute(vector<int>& nums) {if(nums.empty()){return {};}vector<vector<int>> allRet;vector<int> curRet;vector<bool> visited(nums.size(), true);DFS(allRet, curRet, nums, visited);return allRet;}
};

活字印刷

你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。

注意:本题中,每个活字字模只能使用一次。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-tile-possibilities

全排列的变形,此时变为处理字符串,并且返回任意可以印出的组合

1.当前组合不为空, 则插入set中

2.继续给当前组合拼接新的组合,尝试拼接tiles每一个位置的字符

3.如果当前位置已在组合中出现过,跳过该位置,否则标记当前位置,继续拼接更长的组合

4.回溯,尝试组合其它位置,返回

class Solution {
public:void DFS(unordered_set<string>& set, vector<bool>& visited, string& tiles, string cur){//结果不为空则记录下来if(!cur.empty()){set.insert(cur);}for(int i = 0; i < tiles.size(); i++){//如果当前字模已经用过, 则跳过本轮if(visited[i] == false){continue;}//记录当前位cur.push_back(tiles[i]);visited[i] = false;//查找下一位DFS(set, visited, tiles, cur);//回溯, 继续尝试其他组合visited[i] = true;cur.pop_back();}}int numTilePossibilities(string tiles) {if(tiles.empty()){return 0;}unordered_set<string> set;  //结果集, 去重vector<bool> visited(tiles.size(), true);   //标记矩阵DFS(set, visited, tiles, "");return set.size();}
};

N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens

具体思路看注释,主要就是尝试所有排列组合,并判断行,列,左右对角线是否存在其他皇后,然后将所有符合条件的结果保存下来即可。

class Solution {
public:void DFS(vector<vector<pair<int, int>>>& allRes, vector<pair<int, int>>& curRes, int curRow, int n){//此时说明已经走完, 记录结果if(curRow == n){allRes.push_back(curRes);}for(int i = 0; i < n; i++){//如果当前符合规则, 则继续, 如果不符合直接忽略if(isVaild(curRes, curRow, i)){//记录当前行位置curRes.push_back(make_pair(curRow, i));//查找下一行DFS(allRes, curRes, curRow + 1, n);//回溯curRes.pop_back();}}}bool isVaild(vector<pair<int, int>>& curRes, int row, int col){//判断当前位置是否合法, 各皇后不能放在同一条直线上for(const pair<int, int>& cur : curRes){if(cur.first == row                        //判断当前行|| cur.second == col                       //判断当前列|| cur.first + cur.second == row + col       //判断左对角线|| cur.first - cur.second == row - col)       //判断右对角线{return false;}}return true;}//将坐标结果转换为字符串的棋盘vector<vector<string>> resToString(vector<vector<pair<int, int>>>& allRes, int n){vector<vector<string>> res;for(const vector<pair<int, int>>& cur : allRes){vector<string> curRes(n, string(n, '.'));   //初始化棋盘for(const pair<int, int>& pos : cur)    //将皇后放入棋盘中{curRes[pos.first][pos.second] = 'Q';}res.push_back(curRes);  //保存结果}return res;}vector<vector<string>> solveNQueens(int n) {vector<vector<pair<int, int>>> allRes;vector<pair<int, int>> curRes;DFS(allRes, curRes, 0, n);return resToString(allRes, n);}
};

N皇后II

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回 n 皇后不同的解决方案的数量。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens-ii

和上题思路一样,因为只需要返回解决方案的数量,所以不再保存结果,对符合条件的情况进行计数即可。

class Solution {
public:void DFS(vector<pair<int, int>>& curRes, int curRow, int n, int& count){//此时不需要保存结果, 计数就行if(curRow == n){++count;}for(int i = 0; i < n; i++){//如果当前符合规则, 则继续, 如果不符合直接忽略if(isVaild(curRes, curRow, i)){//记录当前行位置curRes.push_back(make_pair(curRow, i));//查找下一行DFS(curRes, curRow + 1, n, count);//回溯curRes.pop_back();}}}bool isVaild(vector<pair<int, int>>& curRes, int row, int col){//判断当前位置是否合法, 各皇后不能放在同一条直线上for(const pair<int, int>& cur : curRes){if(cur.first == row                         //判断当前行|| cur.second == col                       //判断当前列|| cur.first + cur.second == row + col       //判断左对角线|| cur.first - cur.second == row - col)       //判断右对角线{return false;}}return true;}int totalNQueens(int n) {vector<pair<int, int>> curRes;int count = 0;DFS(curRes, 0, n, count);return count;}
};

高级数据结构与算法 | 回溯算法(Back Tracking Method)相关推荐

  1. 回溯java算法_聊聊算法——回溯算法

    "递归只应天上有,迭代还须在人间",从这句话我们可以看出递归的精妙,确实厉害,递归是将问题规模逐渐减小, 然后再反推回去,但本质上是从最小的规模开始,直到目标值,思想就是数学归纳法 ...

  2. 数据结构与算法分析 ——回溯算法之收费公路重建问题

    数据结构与算法分析第十章回溯算法之收费公路重建问题 一.  问题描述: 设给定N个点p1, p2,-,pn,它们位于x-轴上.xi是pi点的x坐标.这N个点确定了N(N-1)/2个点间距离.显然,如果 ...

  3. 数据结构与算法 / 回溯算法(八皇后、0 - 1 背包)

    回溯算法,顾名思义,就是在没有得到最优解的前提下,不断的返回至前面的岔路口位置,重新选择,直至遍历了所有的情况或者得到的预期最优解的情况下再结束. 与贪心算法不同的是,回溯算法理论上是可以得到最优解, ...

  4. 五大常用经典算法—回溯算法

    原文作者:labuladong 原文地址:回溯算法套路详解 读完本文,你可以去力扣拿下如下题目: 46.全排列 51.N皇后 解决一个回溯问题,实际上就是一个决策树的遍历过程.你只需要思考 3 个问题 ...

  5. 理解回溯算法——回溯算法的初学者指南

    0 前言 最近做了不少关于回溯法的算法题,积累了一些心得,这篇博文算是对回溯法的一个小总结. 1 回溯法简介 回溯法简单来说就是按照深度优先的顺序,穷举所有可能性的算法,但是回溯算法比暴力穷举法更高明 ...

  6. 回溯算法 ------回溯算法的几个例子

    1.回溯算法的小结 2.回溯算法的几个例子  2.1  ------ 4后问题 搜索空间:  2.2 ------01背包问题 01背包问题的算法设计 01背包问题的实例分析 01背包问题的搜索空间 ...

  7. 贪心算法+回溯算法+动态规划

    一.贪心算法 1.分饼干问题 #思路:排序加贪心 先让胃口小的孩子满足 class Solution:def findContentChildren(self, g, s):print('==g:', ...

  8. 五大常用算法——回溯算法详解及经典例题

    回溯算法 1.回溯算法就是一种有组织的系统最优化搜索技术,可以看作蛮力法穷举搜索的改进.回溯法常常可以避免搜索所有可能的解,所以它适用于求解组织数量较大的问题. 2.首先我们先了解一下一个基本概念&q ...

  9. PHP实现八皇后算法-回溯算法

    回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就"回溯"返回,尝试别的路径.回溯法是一种选优搜索法,按选优条件向前搜索,以 ...

最新文章

  1. 受用一生的高效PyCharm使用技巧
  2. 强弱AI的辩论:关于人工智能意识的奇妙理论
  3. linux hdparm 测试磁盘io,hdparm测试硬盘性能
  4. python中的迭代库
  5. 今天晴朗,但是由于晚上睡眠不是很好就头昏眼花
  6. 2021CSP:游记
  7. jar包直接拷贝到WEB-INF/lib下和以userLibrary引入的区别
  8. 提升你的开发效率,10 个 NPM 使用技巧
  9. android 图片传递,如何使用包在Android活动之间传递图像(位图)?
  10. 常见蓝屏故障及解决办法
  11. 华硕电池管理软件_多模式无线电竞鼠标怎么选?华硕?ROG 烈刃2 上手体验
  12. OpenStack是吞噬金钱和人力的怪兽吗?
  13. 标准误计算机excel公式,excel标准误差怎么算
  14. N MOSFET VGS(th)和管子导通的关系
  15. 练习3-2,编写一个函数escape(s,t),将字符串t复制到字符串s中,并在复制过程中将换行符、制表符等不可见字符转换为\n、\t等相应的可见的转义字符序列。要求使用switch语句。
  16. 【硬创邦】跟hoowa学做智能路由(十三):网络音箱之Android篇
  17. w ndows10QQ远程,Win10提示“QQ远程系统权限原因,暂时无法操作”的解决方法
  18. putty psftp 上传文件夹或上传文件到linux
  19. OpenHarmony开源鸿蒙与HarmonyOS商业鸿蒙的区别
  20. 银河麒麟V10SP1桌面操作系统安装和配置

热门文章

  1. python办公代码_[Python] 自动化办公 docx操作Word基础代码
  2. 以太坊搭建联盟链详细教程
  3. 微信小程序------MD5加密(支持中文和不支持中文)和网络请求(get和post)
  4. 08 redis数据类型:hash
  5. 使用nohup在后台运行scp
  6. OHCI,UHCI,EOHCI,XHCI
  7. PHP客户端缓存控制
  8. linux-dns的安装
  9. NAGIOS安装指南
  10. 浅析php中的stdClass