回溯算法中常见的使用方法逻辑整理

回溯算法 常见的使用方法逻辑整理


1. 回溯算法 特点

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

2. 回溯算法 通用方法

用回溯算法解决问题的一般步骤:

  • 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
  • 确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
  • 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。

回溯是经过修改的深度优先查找方法,过程包括:对一个状态空间树进行深度优先查找,检查每个节点是否满足条件。如果不满足就回溯到该节点的父节点。算法框架(伪代码)如下:

result = []
backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择

3. 常见面试题

一些总结

3.1 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:
在这里插入图片描述

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true

示例 2:
在这里插入图片描述

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “SEE”
输出:true

示例 3:
在这里插入图片描述

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCB”
输出:false

解法

  • 递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。

  • 终止条件:

    • 返回 false : (1) 行或列索引越界 或 (2) 当前矩阵元素与目标字符不同 或 (3) 当前矩阵元素已访问过 ( (3) 可合并至 (2) ) 。
    • 返回 true : k = len(word) - 1 ,即字符串 word 已全部匹配。
  • 递推工作:

    • 标记当前矩阵元素: 将 board[i][j] 修改为 空字符 ‘’ ,代表此元素已访问过,防止之后搜索时重复访问。
    • 搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
    • 还原当前矩阵元素: 将 board[i][j] 元素还原至初始值,即 word[k] 。
  • 返回值: 返回布尔量 res ,代表是否搜索到目标字符串。

在这里插入图片描述

代码示例

class Solution {public boolean exist(char[][] board, String word) {char[] words = word.toCharArray();for(int i = 0; i < board.length; i++) {for(int j = 0; j < board[0].length; j++) {if (dfs(board, words, i, j, 0)) return true;}}return false;}boolean dfs(char[][] board, char[] word, int i, int j, int k) {if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;if (k == word.length - 1) return true;board[i][j] = '\0';boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);board[i][j] = word[k];return res;}
}

3.2 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

示例 1:

输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]

示例 2:

输入:s = “a”
输出:[[“a”]]

解法

由于需要求出字符串 sss 的所有分割方案,因此我们考虑使用搜索 + 回溯的方法枚举所有可能的分割方法并进行判断。

假设我们当前搜索到字符串的第 iii 个字符,且 s[0…i−1] 位置的所有字符已经被分割成若干个回文串,并且分割结果被放入了答案数组 ans 中,那么我们就需要枚举下一个回文串的右边界 jjj,使得 s[i…j] 是一个回文串。

因此,我们可以从 iii 开始,从小到大依次枚举 jjj。对于当前枚举的 jjj 值,我们使用双指针的方法判断 s[i…j] 是否为回文串:如果 s[i…j]是回文串,那么就将其加入答案数组 ans 中,并以 j+1j+1j+1 作为新的 i 进行下一层搜索,并在未来的回溯时将 s[i…j] 从 ans 中移除。

如果我们已经搜索完了字符串的最后一个字符,那么就找到了一种满足要求的分割方法。

代码示例

class Solution {boolean[][] f;List<List<String>> ret = new ArrayList<List<String>>();List<String> ans = new ArrayList<String>();int n;public List<List<String>> partition(String s) {n = s.length();f = new boolean[n][n];for (int i = 0; i < n; ++i) {Arrays.fill(f[i], true);}for (int i = n - 1; i >= 0; --i) {for (int j = i + 1; j < n; ++j) {f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];}}dfs(s, 0);return ret;}public void dfs(String s, int i) {if (i == n) {ret.add(new ArrayList<String>(ans));return;}for (int j = i; j < n; ++j) {if (f[i][j]) {ans.add(s.substring(i, j + 1));dfs(s, j + 1);ans.remove(ans.size() - 1);}}}
}       

3.3 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

代码示例

class Solution {public List<List<Integer>> permute(int[] nums) {List<List<Integer>> result = new ArrayList<>();List<Integer> input = new ArrayList<>();for (int i: nums) input.add(i);List<Integer> tmp = new ArrayList<>();dfs(input, result, tmp, nums.length);return result;}public void dfs(List<Integer> input, List<List<Integer>> result, List<Integer> temp, int k){if(temp.size()==k) {result.add(temp);return;}for(int i=0;i<input.size();i++){List<Integer> copyInput=new ArrayList<>(input);List<Integer> copyTemp=new ArrayList<>(temp);copyTemp.add(input.get(i));copyInput.remove(i);dfs(copyInput,result,copyTemp,k);}}
}    

3.4 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

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

示例 2:

输入:nums = [0]
输出:[[],[0]]

解法思路

整体上跟跳跃游戏一致,只是稍作变通

代码示例

class Solution {public List<List<Integer>> subsets(int[] nums) {List<List<Integer>> res = new ArrayList<>();dfs(0, nums, res,new ArrayList<Integer>());return res;}public void dfs(int i, int[] nums, List<List<Integer>> res,  ArrayList<Integer> tmp) {res.add(new ArrayList<>(tmp));for(int j = i; j < nums.length;j++) {tmp.add(nums[j]);dfs(j + 1, nums, res, tmp);tmp.remove(tmp.size() -1);}}
}

3.5 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

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

在这里插入图片描述

示例 1:

输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:

输入:digits = “”
输出:[]
示例 3:

输入:digits = “2”
输出:[“a”,“b”,“c”]

代码示例

class Solution {public List<String> letterCombinations(String digits) {// 定义相关数字和字母的mapping关系Map<String, String[]> map = new HashMap<String, String[]>(){{put("2", new String[]{"a","b","c"});put("3", new String[]{"d","e","f"});put("4", new String[]{"g","h","i"});put("5", new String[]{"j","k","l"});put("6", new String[]{"m","n","o"});put("7", new String[]{"p","q","r","s"});put("8", new String[]{"t","u","v"});put("9", new String[]{"w","x","y","z"});}};// 逃出条件1:什么都没有输入,返回空if(digits.length() == 0) {return new ArrayList<>();}// 逃出条件2: 只输入1个数字,返回对应的mapping字母列表if(digits.length() == 1) {List<String> arrayList = new ArrayList<>(map.get(digits).length);Collections.addAll(arrayList, map.get(digits));return arrayList;}String[] strList = digits.split("");// 构造递归条件,将完整的输入拆分成2部分String[] first = map.get(strList[0]);String rest = "";for(int i = 1;i < strList.length;i++) {rest += strList[i];}List<String> result = new ArrayList<>();// 分别进行拼凑,进行递归所有的数字for(int i=0;i<first.length;i++) {List<String> tmp = letterCombinations(rest);for(int j = 0;j<tmp.size();j++) {result.add(first[i] + tmp.get(j));}}return result;}
}

3.6 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:

输入: candidates = [2], target = 1
输出: []

代码示例

class Solution {public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();Arrays.sort(candidates);dfs(candidates, target, res, 0, new ArrayList<Integer>());return res;}public void dfs(int[] candidates, int target, List<List<Integer>> res, int i ,List<Integer> tmp) {if(target < 0) return;if(target == 0) {res.add(new ArrayList<>(tmp));return;}   for(int start = i; start < candidates.length;start++) {tmp.add(candidates[start]);dfs(candidates, target - candidates[start], res,start, tmp);tmp.remove(tmp.size() - 1);}}
}

3.7 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]

示例 2:

输入:n = 1
输出:[“()”]

代码示例

class Solution(object):def generateParenthesis(self, n):""":type n: int:rtype: List[str]"""""":type n: int:rtype: List[str]"""global resultresult = []bracket_str = ""self.find_bracket(n, n, bracket_str)print(result)return resultdef find_bracket(self, left, right, bracket_str):""""""global resultif left == 0 and right == 0:result.append(bracket_str)if left > right:returnif left > 0:self.find_bracket(left - 1, right, bracket_str + '(')if right > 0:self.find_bracket(left, right - 1, bracket_str + ')')

4. 参考文档

暂无,相关面试题请参考leetcode以及相关说明

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/619421.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CentOS 7安装Nginx

说明&#xff1a;本文介绍如何在CentOS 7操作系统中安装Nginx 下载安装 首先&#xff0c;去官网上下载Nginx压缩包&#xff0c;官网地址&#xff1a;https://nginx.org/en/download.html&#xff0c;我这里下载稳定版1.24.0&#xff1b; 上传到云服务器上&#xff0c;解压&am…

Vue入门:天不生Vue,前端万古如长夜 - Vue从入门到放弃

目录 &#x1f44b; Vue环境搭建 1.安装node.js 2.配置环境变量 3.VSCode配置 4.安装Vue CLI 5.在VS Code中打开Vue项目 6.运行Vue项目 &#x1f440; Vue基础学习 1.引入vue.js 2.数据方法 3.生命周期&#xff01; 4.模板语法 5.对象语法 6.条件渲染 7.列表渲…

openstack安装dashboard后登录网页显示404错误

1. 2.进入该目录vim /etc/httpd/conf.d/openstack-dashboard.conf 增加这一行 WSGIApplicationGroup %{GLOBAL} 重启httpd后就可以访问了

Oracle 19c RAC 补丁升级 补丁回退

补丁升级流程 补丁升级 停止集群备份家目录 两节点分别操作 cd /u01/app/19.3.0/grid/bin/ crsctl stop crs tar -zcvf /u01/app.tar.gz /u01/app /u01/app/19.0.0/grid/bin/crsctl start crs 两节点OPatch替换 --- 表示 root 用户&#xff0c;$ 表示 Oracle 用户提示符&#…

QT、ffmpeg视频监控分屏

1、支持分屏&#xff08;4&#xff0c;6&#xff0c;8&#xff0c;9&#xff0c;13&#xff0c;16&#xff0c;25&#xff0c;32&#xff0c;64&#xff09;切换 2、支持拖拽效果 3、支持播放mp4&#xff0c;rtmp等 4、本人亲测支持播放32路&#xff0c;64路没做测试 5、支持读…

计算机网络—传输层UDP协议:原理、应用

​ &#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;2月のセプテンバー 1:21━━━━━━️&#x1f49f;──────── 5:21 &#x1f504; ◀️ ⏸ ▶️ ☰ &am…

Github 2024-04-15 开源项目日报Top10

根据Github Trendings的统计,今日(2024-04-15统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4TypeScript项目2HTML项目1JavaScript项目1C++项目1Rust项目1Mojo项目1Fooocus: 图像生成软件 创建周期:188 天开发语言:Python协议…

基于SSM微信小程序的四六级词汇系统

采用技术 基于SSM微信小程序的四六级词汇系统的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringMVCMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 用户客户端 注册用户界面 首页界面 我的界面 用户信息界…

SqlServer专题

目录 1&#xff0c;连接数据库 2&#xff0c;连接池 1.何为连接池&#xff1f; 2.连接池运行原理。 3.如何查看连接池&#xff1f; 4.连接池注意事项。 3&#xff0c;一般SQL语句。 4&#xff0c;控制语句 1.判断语句 2.循环语句 5&#xff0c;视图 1.使用…

Spark 应用程序优化和调优总结

文章目录 前言调整 Spark 默认配置查看和设置 Spark 配置信息动态扩展集群负载 数据的缓存和持久化DataFrame.cache()DataFrame.persist()何时缓存和持久化何时不缓存和持久化 Spark 中的 JOINs广播连接排序合并连接 总结 前言 本文总结了 Spark 中比较重要和常用的调优手段&a…

RedisTemplate

3.3.RedisTemplate 在Sentinel集群监管下的Redis主从集群&#xff0c;其节点会因为自动故障转移而发生变化&#xff0c;Redis的客户端必须感知这种变化&#xff0c;及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。 下面&#xff0c;我们…

一二三应用开发平台使用手册——系统管理-用户-使用说明

概述 对于新系统&#xff0c;如果说初始化组织机构是第一步工作&#xff0c;那么初始化用户则是第二步工作。今天来介绍系统管理模块中的用户使用说明。 对于企业应用&#xff0c;人员是重要的基础数据。 在不少平台和系统中&#xff0c;对“人”的概念进一步细分&#xff0c;…