当前位置: 首页 > news >正文

leetcode hot100:十一、解题思路大全:回溯(全排列、子集、电话号码的字母组合、组合总和、括号生成、单词搜索、分割回文串、N皇后)

我太爱这种回溯了,多做几次就熟了的感觉,别管,已膨胀(

全排列

给定一个不含重复数字的数组 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]]

思路

回溯终止条件是path长度==nums长度,因为是全排列,所以i遍历数组时每次都会从头遍历(因为全排列不在乎数字的先后顺序),所以需要用used数组记录这个数字是否已经在path中使用过。

子集

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

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

示例 1:

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

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

思路

回溯终止条件依旧是path长度==nums长度。但是子集要求是有顺序的,所以i遍历数组时不会每次都会从头遍历,相反的是每次回溯我们都需要用cur来记录i之前已经遍历到的位置,这次回溯再从这个cur(cur为i+1,保证了不会选到重复的数字)开始,所以也就不再需要used数组记录。

电话号码的字母组合

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

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

在这里插入图片描述

示例 1:

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

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

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

思路

相当于子集那道题多了一个字母映射。

组合总和

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

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

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

思路

相当于给你一个整数数组,数组中的元素互不相同,返回该数组所有可能的子集,**要求子集的和为target。解集不可以包含重复的子集,但可以包含重复的数字。**你可以按 任意顺序 返回解集。

和子集那道题对比,子集那道题如下:

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

相当于子集那道题的基础上多了两个加粗的条件。为此,我们的回溯条件改为
子集的和为target就加入答案并返回,如果子集的和已经>target,就直接返回(说明要回溯)。
除此之外,虽然解集可以包含重复的数字,但依旧是不包含重复的子集的,所以我们依旧要维护cur。 通过 cur,每次递归只能从当前位置或之后的元素开始选择,避免生成重复的组合。例如,当你已经生成组合 [2,2,3] 后,不会再生成 [2,3,2] 或 [3,2,2]。

而回溯时,我们的cur传入 i 而非 i+1,保障了可以选取到重复的数字。

在这再次贴一下子集那道题的思路:
回溯终止条件依旧是path长度==nums长度。但是子集要求是有顺序的,所以i遍历数组时不会每次都会从头遍历,相反的是每次回溯我们都需要用cur来记录i之前已经遍历到的位置,这次回溯再从这个cur(cur为i+1,保证了不会选到重复的数字)开始,所以也就不再需要used数组记录。

括号生成

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

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

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

提示:

1 <= n <= 8

思路

这道题有点意思啊hhh

有效括号的条件:

  • 平衡性:在生成过程中,左括号 ( 的数量必须始终大于等于右括号 ) 的数量。
  • 完整性:最终生成的字符串中,左括号和右括号的数量均为 n。

回溯法的实现:
递归路径:在每一步递归中,可以选择添加左括号或右括号,但需要满足上述条件。

剪枝条件:

  • 当左括号数量小于 n 时,可以添加左括号。
  • 当右括号数量小于左括号数量时,可以添加右括号。

算法步骤
递归函数参数:当前字符串 path、左括号数量 left、右括号数量 right、目标对数 n、
结果列表 result。

终止条件:当 left = n 且 right = n 时,将当前字符串加入结果列表。

递归选择:

  • 添加左括号:若 left < n,添加 ( 并递归 left + 1。
  • 添加右括号:若 right < left,添加 ) 并递归 right + 1。

单词搜索

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

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

思路

标准DFS回溯。找到word[0] 的单元格 (i, j) 作为起点。

从每个起点出发,递归检查四个方向:

终止条件:

  • 若当前字符索引 k 等于单词长度,说明单词已完全匹配,返回 True。
  • 若越界、字符不匹配或单元格已被访问,返回 False。

递归过程:

  • 标记当前单元格为已访问(例如,将 board[i][j] 改为特殊字符 #)。
  • 递归搜索四个方向,只要有一个方向返回 True,则整体返回 True。
  • 回溯:恢复当前单元格为原始值,继续尝试其他方向。

分割回文串

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

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

示例 2:
输入:s = “a”
输出:[[“a”]]

提示:
1 <= s.length <= 16
s 仅由小写英文字母组成

思路

其实这道题一眼看过去,会想到动态规划来做的。毕竟dp擅长处理这种子串分割的问题。

用二维数组 dp[i][j] 表示子串 s[i:j+1] 是否为回文串。

状态转移方程:
dp[i][j] = (s[i] == s[j]) and (j - i <= 2 or dp[i+1][j-1])
即当前字符相等,且子串长度小于等于 2 或去掉首尾后仍为回文。

但是纯 DP 通常更适合计算 “方案数量”,而本题要求生成所有具体的分割方案,所以即使用了动态规划,我们还是需要用回溯来生成具体的分割方案的。

譬如分割等和子集那道题:给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
就是可以用纯dp来做。

如果这道题我们真的要用纯dp来做的话。思路如下:

状态转移:
对于每个位置 i,遍历所有可能的分割点 j(0 ≤ j < i),如果子串 s[j:i] 是回文,则将 dp[j] 中的每个方案末尾添加 s[j:i],并加入 dp[i]。
路径记录:
在 DP 数组中,每个 dp[i] 存储一个列表,列表中的每个元素是一个分割方案(即字符串列表)。
时间复杂度超高!O(n^2 * 2^n)

所以我们用回溯法直接来做。
回溯函数的参数有两个,一个是cur,我们用cur维护分割点,一个是答案集合path。

cur:表示当前递归层的起始分割点,即从哪个位置开始尝试分割新的回文子串。
i:表示当前递归层的结束分割点,用于生成子串 s[cur:i+1] 并检查其是否为回文。

如果生成的子串是回文的,那么我们加入答案集和path,并且继续尝试新的起始分割点(cur=i+1)。(注意这里没有显式地去判断尝试新的结束分割点,但是实际上通过外层循环,我们是会去判断的)。如果生成的子串不是回文的,我们尝试新的结束分割点(i=i+1)。

直到达到回溯边界条件:起始分割点达到字符串终点,说明整个字符串已被成功分割为回文子串,记录结果并终止递归。

在这一个注意点是,我们为什么不需要判断 s[0:cur] 是否回文?
这是因为每次递归调用时,cur 参数表示当前待处理的子串起始位置。在递归进入下一层前,我们已经确保了:

  • 当前路径 path 中的所有子串都是回文(因为加入 path 前已检查)。
  • path 中的子串恰好覆盖 s[0:cur](因为每次递归时 cur 递增,且子串无重叠)。

因此,s[0:cur] 的合法性已经由历史递归步骤保证,无需重复检查。

示例说明:

假设当前递归层 cur=3,路径 path = [“aa”, “b”],说明:
s[0:2] = “aa” 是回文(已在 cur=0 时检查)。
s[2:3] = “b” 是回文(已在 cur=2 时检查)。
因此,s[0:3] 已被合法分割为回文子串,无需再次验证。

也就是说,两层循环,外层循环i(结束分割点),内层通过递归循环cur(起始分割点)。
外层循环负责在当前起始位置 cur 下,尝试所有可能的子串(通过 i 扩展结束位置)。
递归调用负责在找到一个合法子串后,固定该子串,并从下一个位置 i+1 开始处理剩余字符串。

N皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

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

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

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

思路

这个需要说明吗?需要题解吗?
黎吧皇,你很有名, 我就不给思路了。hhh

相关文章:

  • lanqiaoOJ 4330:欧拉函数模板
  • UG903 (v2024.2)的中文版
  • 计算机视觉(CV)中的视觉定位与外观检测技术解析
  • 2025 最新教程:注册并切换到美区 Apple ID
  • OSS对象存储如何避免被攻击恶意刷流量?
  • echarts之折线柱状图
  • 【普及+/提高】洛谷P2613 ——【模板】有理数取余
  • bi平台是什么意思?bi平台具体有什么作用?
  • 【递归、搜索与回溯算法】专题三:穷举vs暴搜vs深搜vs回溯vs剪枝
  • 文章记单词 | 第112篇(六级)
  • 交安公路水运安全员考试的科目有哪些?
  • 从外部平台通过接口获取数据,初始全量同步 和 后续带版本管理的增量同步
  • 常见证书格式区别
  • SOC-ESP32S3部分:9-GPIO输入按键状态读取
  • 提示词工程框架——CO-STAR 框架实战
  • Unity 踩坑记录 UnityWebRequest.Delete(url) downloadHandler 空指针
  • [原理理解] 基于diffusion的超分任务时候遇到的CLIPTokenizer和CLIPTextModel
  • 通过vps命令行向dropbox上传文件
  • lua脚本实战—— Redis并发原子性陷阱
  • 文章记单词 | 第102篇(六级)
  • 鞋子网站模板/北京线上教学
  • 建设厅职业资格中心网站/免费b2b推广网站
  • 海洋cms/seo关键词优化怎么收费
  • 武汉建设工程律师/优化网站最好的刷排名软件
  • 国土局网站建设制度/搜索引擎优化课程
  • 湖北省电力建设三公司网站/杭州seo软件