代码随想录:回溯算法
组合
|
|
使用 递归 + 回溯 的算法。
在树的第 1 层,选择一个 start 作为本次递归的起始值,start 从 1 开始,到 n 结束。
在下层递归中,start 从上层的后一个数开始(这样就保证了得到的组合不会有重复的情况)。
将每层选择的元素组合成一个一维数组 arr,如果 arr 的长度为 2,则可以终止递归,向上回溯了。
|
|
组合总和III
|
|
本题和上面一个题差不多,也是本层递归选定一个数,下一层递归又选定一个数。
|
|
电话号码的字母组合
|
|
先将数字和其对应的字符集合做映射,可以用 map 或数组,这里就采用数组。
然后遍历 digits,digits[0] 对应的字符集合做递归的第 1 层。
每层递归就将 digits 的下标后移一位,我们可以定义一个 index 表示当前遍历到 digits 的下标。
|
|
组合总和
|
|
本题和 组合 的题目差不多,但需要注意的是,数组中的元素是可以重复使用的。
所以在下次递归时,传入的参数是 i 而不是 i+1。
为了防止出现重复的结果集,我们也要使用 startIndex 参数来控制递归的开始位置。
|
|
组合总和II
|
|
本题的和 组合 不同的地方在于,候选数组 candidates 中存在相同的元素。
我们使用 arr 数组保存遍历过的元素,在 arr 中,我们可以取相同的元素,但 res 中不能有重复的组合 如何去重呢?
这涉及到 树枝去重 和 树层去重。
树枝去重就是对 arr 选择的元素去重,显然,本题允许 arr 中存在重复值,所以不能对树枝进行去重;
树层去重就是某次遍历中出现某个元素时,在后面的遍历中又出现了相同的元素,那么我们就应该跳过后面本次遍历过程,因为前面的遍历已经包含了后面遍历的过程。
具体实现:先对候选数组排序,然后使用 used 数组来标记每个下标的元素是否已经用过了;
如果 candidates[i] 和 candidates[i-1] 相等(出现了重复元素),如果 used[i-1]==0(说明是树层重复),那么就应该跳过本次递归。
used[i-1]==1 是树枝重复,在后面的遍历中,会回溯,将 used[i] 置为 0。
|
|
分割回文串
|
|
本题和 组合 问题差不多,都是每层递归,选择元素的位置往后移动 1 位我们使用 start 表示我们本次切割的位置。
在每层递归遍历中,对 start 后面的字符串进行切割,判断是否是回文字符串,切割的区间就是 start 到 i(左闭右闭)。
|
|
复原IP地址
|
|
本题和 分割回文串 差不多,也是每层递归,截取字符串的起始位置 start 往后移一位。
每层遍历中,遍历 start 往后的字符串,判断 start 到 i(左闭右闭)区间的字符串是否合法。
如果不合法,直接跳过本次递归;如果合法,将区间内的字符串加入到 arr 中。
我们还需要统计现在已经添加分割点 ( . ) 的个数 dotCount,如果大于等于三个,退出本次递归(递归出口)。
当 dotCount 等于 3 时,判断后面剩下的字符串是否符合 ip 的要求,如果不合法,直接 return,如果合法,将 arr 和后面的字符串一起加入到 res 中。
|
|
子集
|
|
本题和 组合 差不多,也是每层递归选择一个元素,start 表示本次递归的起始位置。
但本题和 组合 问题不一样的地方在于,本题的结果分布在树的各个结点,而不是叶子结点,所以我们不能再终止条件(叶子结点)处收获结果,而应该在每层递归时将结果添加到 res 中。
|
|
子集II
|
|
本题和 组合总和II 差不多,都是候选数组中存在重复元素,但最后的结果集不能有重复的组合。
我们需要进行树层去重,使用 used 数组标识某个位置的元素是否已经使用过;
如果没有使用过(说明是回溯之后的,也就是树层出现了重复值),且存在和前一个值相同的情况,那么就应该去重(直接跳过本次递归)。
|
|
非递减子序列
|
|
本题也需要树层去重,由于是子序列,所以我们不能对原数组进行排序。
对树层去重的逻辑是判断这个是否之前出现过(需要用到 set),如果出现过的话,那么就可以进行树层去重。
为了达到树层去重的效果,需要在递归中定义 set,而不是使用全局变量。 因为该 set 应该只对本层递归有效(在 for 循环之前定义),而不是全局有效,如果改成全局变量,无法判断是树层重复还是树枝重复。
我们需要判断当前遍历到的数是否大于等于 arr 的最后一个数,如果是(非递减),则将该数加入到 arr 中。
|
|
全排列
|
|
排列问题和 组合 问题不同的地方在于,在递归的过程中,排列问题可以取之前取过的元素,但同样的,每个元素也不能重复使用。
我们可以定义一个 used 数组,来标记递归中已经使用过的元素下标。
|
|
全排列II
|
|
本题需要先对 nums 排序,然后树层去重。
|
|
N皇后(困难)
|
|
本题和前面的排列、组合、分割不同的地方在于,这是一个二维数组,而不是一维数组。
我们需要定义一个二维数组 chessboard,表示棋盘。
我们可以使用 row 表示我们当前遍历到的行下标,每层递归的 row 增加 1。
当 row 等于等于 n-1 的时候,就是递归的出口。
|
|
解数独(困难)
|
|
本题和 N 皇后都是二维棋盘问题。
但不同的地方在于,N 皇后可以每层递归扫码一行数据。
而数独问题不能扫描一行,而需要扫描每个具体的棋盘位置,判断该位置能否放置指定的数字。
所以在递归函数中,需要使用两个 for 循环,来分别遍历行、列的位置。
|
|