Leetcode 刷题笔记(二十) ——回溯算法篇之分割、子集、全排列问题
作者:互联网
文章目录
系列文章目录
一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的层序遍历及相关题目
十 四、二叉树篇之二叉树的属性相关题目
十 五、 二叉树篇之二叉树的修改与构造
十 六、 二叉树篇之二叉搜索树的属性
十 七、二叉树篇之公共祖先问题
十 八、二叉树篇之二叉搜索树的修改与构造
十 九、回溯算法篇之组合问题
更新中 … …
前言
刷题路线来自 :代码随想录
切割问题: 一个字符串按一定规则有几种切割方式
子集问题: 一个N个数的集合里有多少符合条件的子集
排列问题: N个数按一定规则全排列,有几种排列方式
题录
131. 分割回文串
Leetcode 链接
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
题解:
- 递归参数:可以传入 s,然后传入下层递归开始的下标,然后在下层分割字符串,也可以将字符串分割好作为参数,这里选择后者
- 收集结果:s 字符串无法再分割,即长度为 0 时(同结束条件)
- 剪枝:分割的部分子串不是回文串,continue 进行下次分割
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> list = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s);
return res;
}
public void backtracking(String s) {
if (s.length() == 0) {
res.add(new ArrayList<>(list));
return;
}
// s 为没有分割过的子串,从头分割
for (int i = 1; i <= s.length(); i++) {
// 分割部分不是回文串
if (!isE(s, 0, i)) continue;
list.add(s.substring(0, i));
// s.substring(i, s.length()):下层递归分割
backtracking(s.substring(i, s.length()));
// 回溯
list.remove(list.size() - 1);
}
}
// 判断回文串
public boolean isE(String s, int l, int r) {
r--;
while (l < r) {
if (s.charAt(l) != s.charAt(r)) {
return false;
}
l++;
r--;
}
return true;
}
}
93. 复原 IP 地址
Leetcode 链接
题解: 相比上一题,细节繁多
- 回溯对象:StringBuilder 拼接 IP ,拼接前记录下长度,回溯时作为删除末尾子串的下标起始位置
- 收集结果:刚好分割为 4 个整数,并且字符串刚好分割完
- 返回列表:接下来要分割是子串 s,conut 记录分割的整数个数
- 结束条件/剪枝:分割的整数大于 255、前导有 0、分割的整数个数大于 4
class Solution {
List<String> res = new ArrayList<>();
StringBuilder sb = new StringBuilder();
public List<String> restoreIpAddresses(String s) {
backtracking(s, 0);
return res;
}
public void backtracking(String s, int count) {
if (s.length() == 0 && count == 4) {
// 刚好分割为 4 个整数,删除拼接时的'.',添加到返回列表
sb.deleteCharAt(sb.length() - 1);
res.add(new String(sb));
return;
}
for (int i = 1; i <= s.length() && (count < 4); i++) {
int num = Integer.valueOf(s.substring(0, i));
// 结束条件,还有一个放在了for 循环的判断中
if ((i > 1 && s.charAt(0) == '0') || num > 255) {
return;
}
// 记录拼接前下标用来回溯
int idx = sb.length();
// 拼接
sb.append(num).append('.');
backtracking(s.substring(i, s.length()), count + 1);
// 回溯
sb.delete(idx, sb.length());
}
}
}
78. 子集
Leetcode 链接
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
题解:
- 回溯对象:路径
- 收集结果:收集路径上的所有结果,注意还有一个额外的空集
- 结束条件:自然结束,因为套搜索到所有结果
- 参数列表:每层递归数组的起始位置
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
res.add(path);
backtracking(nums, 0);
return res;
}
public void backtracking(int[] nums, int start) {
for (int i = start; i < nums.length; i++) {
// 收集路径上的所有结果
path.add(nums[i]);
res.add(new ArrayList<>(path));
// 是 i + 1, 不是 start + 1
backtracking(nums, i + 1);
// 回溯
path.remove(path.size() - 1);
}
}
}
90. 子集 II
Leetcode 链接
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
题解: 相比上题,需要增加去重操作
- 回溯对象:路径
- 收集结果:同上题收集路径上所有结果
- 结束条件/剪枝:自然结束
- 去重:排序后,同层第一次出现的数不用去重,连续出现的数除第一个外全部 continue 跳过
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
res.add(path);
Arrays.sort(nums);
backtracking(nums, 0);
return res;
}
public void backtracking(int[] nums, int start) {
for(int i = start; i < nums.length; i++) {
if (i > start && nums[i] == nums[i - 1]) {
// 同层不是第一次出现
continue;
}
path.add(nums[i]);
res.add(new ArrayList<>(path));
backtracking(nums, i + 1);
path.remove(path.size() - 1);
}
}
}
46. 全排列
Leetcode 链接
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
题解:
- 回溯对象:路径,boolean[ ] used
- 收集结果:路径长度等于 数组长度 (同结束条件)
- 递归参数:因为全排列每层递归从头开始遍历,只需跳过已经过的数,无需 startIndex
- 去重操作:每一层递归可用的数都在减少,上题去重只需一个判断,而这里去重的数太多,所以要使用 set 来记录,当然我们也可以使用数组 boolean[ ] used 当遇到已经使用过的数,continue 跳过
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
backtracking(nums);
return res;
}
public void backtracking(int[] nums) {
if (path.size() == nums.length) {
// 长度判断,收集结果
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i] == true) {
// 使用过
continue;
}
// 记录新的路径,并标记已经使用过
path.add(nums[i]);
used[i] = true;
backtracking(nums);
// 回溯
path.remove(path.size() - 1);
used[i] = false;
}
}
}
47. 全排列 II
Leetcode 链接
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
题解:
- 回溯对象:路径,boolean[ ] used
- 收集结果:路径长度等于 数组长度 (同结束条件)
- 递归参数:因为全排列每层递归从头开始遍历,只需跳过已经过的数,无需 startIndex
- 去重操作:
1.每一层递归可用的数都在减少,上题去重只需一个判断,而这里去重的数太多,所以要使用 set 来记录,当然我们也可以使用数组 boolean[ ] used 当遇到已经使用过的数,continue 跳过
2.层去重,同子集二,先排序再判断是否当前层第一次出现
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
used = new boolean[nums.length];
Arrays.sort(nums);
backtracking(nums);
return res;
}
public void backtracking(int[] nums) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i] == true) {
continue;
}
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
path.add(nums[i]);
used[i] = true;
backtracking(nums);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
491. 递增子序列
Leetcode 链接
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
题解:
- 回溯对象:路径
- 收集结果:路径长度 大于等于 2,收集结果完不可以 return
- 递归参数:因为全排列每层递归从头开始遍历,只需跳过已经过的数,无需 startIndex
- 结束条件/剪枝:不递增时 continue,注意不递增的判断 是 path 的最后一个数和当前数对比
- 去重操作:因为这里的数组无序切不能排序,使用 set 进行层去重,发现同层不是第一次出现 continue。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums, 0);
return res;
}
public void backtracking(int[] nums, int startIndex) {
if (path.size() >= 2) {
res.add(new ArrayList<>(path));
}
Set<Integer> set = new HashSet<>();
for (int i = startIndex; i < nums.length; i++) {
// if (i > 0 && nums[i - 1] > nums[i]) { 错误示范
// continue;
// }
// 递增判断
if (!path.isEmpty() && path.get(path.size() - 1) > nums[i]) {
continue;
}
// 去重
if (set.contains(nums[i])) {
continue;
}
set.add(nums[i]);
path.add(nums[i]);
backtracking(nums, i + 1);
// 回溯
path.remove(path.size() - 1);
}
}
}
标签:nums,int,res,new,回溯,path,Leetcode,backtracking,刷题 来源: https://blog.csdn.net/a1241692733/article/details/123029578