参考:
题型一:排列、组合、子集相关问题
提示:这部分练习可以帮助我们熟悉「回溯算法」的一些概念和通用的解题思路。解题的步骤是:先画图,再编码。去思考可以剪枝的条件, 为什么有的时候用 used 数组,有的时候设置搜索起点 begin 变量,理解状态变量设计的想法。
46全排列(中等)
47全排列 II(中等):思考为什么造成了重复,如何在搜索之前就判断这一支会产生重复;
39组合总和(中等)
40组合总和 II(中等)
77组合(中等)
78子集(中等)
90子集 II(中等):剪枝技巧同 47 题、39 题、40 题;
60第 k 个排列(中等):利用了剪枝的思想,减去了大量枝叶,直接来到需要的叶子结点;
93复原 IP 地址(中等)
题型二:Flood Fill
提示:Flood 是「洪水」的意思,Flood Fill 直译是「泛洪填充」的意思,体现了洪水能够从一点开始,迅速填满当前位置附近的地势低的区域。类似的应用还有:PS 软件中的「点一下把这一片区域的颜色都替换掉」,扫雷游戏「点一下打开一大片没有雷的区域」。
下面这几个问题,思想不难,但是初学的时候代码很不容易写对,并且也很难调试。我们的建议是多写几遍,忘记了就再写一次,参考规范的编写实现(设置 visited 数组,设置方向数组,抽取私有方法),把代码写对。
733图像渲染(Flood Fill,中等)
200 岛屿数量(中等)
130被围绕的区域(中等)
79单词搜索(中等)
说明:以上问题都不建议修改输入数据,设置 visited 数组是标准的做法。可能会遇到参数很多,是不是都可以写成成员变量的问题,面试中拿不准的记得问一下面试官
题型三:字符串中的回溯问题
提示:字符串的问题的特殊之处在于,字符串的拼接生成新对象,因此在这一类问题上没有显示「回溯」的过程,但是如果使用 StringBuilder 拼接字符串就另当别论。
在这里把它们单独作为一个题型,是希望朋友们能够注意到这个非常细节的地方。
17 电话号码的字母组合(中等);
784字母大小写全排列(中等);
22括号生成(中等) :这道题广度优先遍历也很好写,可以通过这个问题理解一下为什么回溯算法都是深度优先遍历,并且都用递归来写。
题型四:游戏问题
回溯算法是早期简单的人工智能,有些教程把回溯叫做暴力搜索,但回溯没有那么暴力,回溯是有方向地搜索。「力扣」上有一些简单的游戏类问题,解决它们有一定的难度,大家可以尝试一下。
51N 皇后(困难):其实就是全排列问题,注意设计清楚状态变量,在遍历的时候需要记住一些信息,空间换时间;
37解数独(困难):思路同「N 皇后问题」;
488祖玛游戏(困难)
529扫雷游戏(困难)
46. 全排列(中等)
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
// 使用一个动态数组保存所有可能的全排列
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
boolean[] used = new boolean[len];
List<Integer> path = new ArrayList<>();
dfs(nums, len, 0, path, used, res);
return res;
}
private void dfs(int[] nums, int len, int depth,
List<Integer> path, boolean[] used,
List<List<Integer>> res) {
if (depth == len) {
res.add(new ArrayList<>(path));
return;
}
// 在非叶子结点处,产生不同的分支,这一操作的语义是:在还未选择的数中依次选择一个元素作为下一个位置的元素,这显然得通过一个循环实现。
for (int i = 0; i < len; i++) {
if (!used[i]) {
path.add(nums[i]);
used[i] = true;
dfs(nums, len, depth + 1, path, used, res);
// 注意:下面这两行代码发生 「回溯」,回溯发生在从 深层结点 回到 浅层结点 的过程,代码在形式上和递归之前是对称的
used[i] = false;
path.remove(path.size() - 1);
}
}
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
Solution solution = new Solution();
List<List<Integer>> lists = solution.permute(nums);
System.out.println(lists);
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
47. 全排列 II(排序后剪枝)
47. 全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
注意先排序 Arrays.sort(nums);
剪枝:
if(i>0&&nums[i]==nums[i-1]&& !used[i-1]){continue;}
class Solution {
List<List<Integer>> res =new ArrayList<>();
List<Integer> path=new ArrayList<>();
boolean used[];
public List<List<Integer>> permuteUnique(int[] nums) {
if(nums.length==0){
return res;
}
Arrays.sort(nums);
used=new boolean[nums.length];
dfs(nums,0);
return res;
}
public void dfs(int[] nums,int depth){
if(depth==nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++){
if(used[i]){
continue;
}
if(i>0&&nums[i]==nums[i-1]&& !used[i-1]){
continue;
}
path.add(nums[i]);
used[i]=true;
dfs(nums,depth+1);
used[i]=false;
path.remove(path.size()-1);
}
}
}
39. 组合总和
给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
int len=candidates.length;
if(len<=0){
return res;
}
Arrays.sort(candidates);
dfs(candidates,0,target);
return res;
}
public void dfs(int[] candidates,int begin,int target){
if(target==0){
res.add(new ArrayList<>(path));
return;
}
for(int i=begin;i<candidates.length;i++){
if(target-candidates[i]<0){
break;
}
path.add(candidates[i]);
dfs(candidates,i,target-candidates[i]);
path.remove(path.size()-1);
}
}
}
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
和39对比:
// 同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue
if (i > begin && candidates[i] == candidates[i - 1]) {
continue;
}
递归传递下去的是 i + 1 而不是 i
dfs(candidates,i+1,target-candidates[i]);
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
int len=candidates.length;
if(len<=0){
return res;
}
Arrays.sort(candidates);
dfs(candidates,0,target);
return res;
}
public void dfs(int[] candidates,int begin,int target){
if(target==0){
res.add(new ArrayList<>(path));
return;
}
for(int i=begin;i<candidates.length;i++){
if(target-candidates[i]<0){
break;
}
if(i>begin&&candidates[i]==candidates[i-1]){
continue;
}
path.add(candidates[i]);
dfs(candidates,i+1,target-candidates[i]);
path.remove(path.size()-1);
}
}
}
93. 复原 IP 地址
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
示例 1:
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
class Solution {
List<String> result = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
if (s.length() > 12) return result; // 算是剪枝了
backTrack(s, 0, 0);
return result;
}
// startIndex: 搜索的起始位置, pointNum:添加逗点的数量
private void backTrack(String s, int startIndex, int pointNum) {
if (pointNum == 3) {// 逗点数量为3时,分隔结束
// 判断第四段⼦字符串是否合法,如果合法就放进result中
if (isValid(s,startIndex,s.length()-1)) {
result.add(s);
}
return;
}
for (int i = startIndex; i < s.length(); i++) {
if (isValid(s, startIndex, i)) {
s = s.substring(0, i + 1) + "." + s.substring(i + 1); //在str的后⾯插⼊⼀个逗点
pointNum++;
backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
pointNum--;// 回溯
s = s.substring(0, i + 1) + s.substring(i + 2);// 回溯删掉逗点
} else {
break;
}
}
}
// 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
private Boolean isValid(String s, int start, int end) {
if (start > end) {
return false;
}
if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
return false;
}
if(end-start>=3){
return false;
}
for (int i = start; i <= end; i++) {
if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
return false;
}
int a=Integer.parseInt(s.substring(start,end+1));
if(a>255){
return false;
}
}
return true;
}
}
作者:carlsun-2
链接:https://leetcode-cn.com/problems/restore-ip-addresses/solution/dai-ma-sui-xiang-lu-93-fu-yuan-ip-di-zhi-pzjo/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
78 子集
78子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
class Solution {
List<List<Integer>> res =new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
if(nums.length==0){
res.add(new ArrayList<>());
return res;
}
dfs(nums,0);
return res;
}
public void dfs(int[] nums,int startIndex){
res.add(new ArrayList<>(path));
if(startIndex>=nums.length){
return ;
}
for(int i=startIndex;i<nums.length;i++){
path.add(nums[i]);
dfs(nums,i+1);
path.remove(path.size()-1);
}
}
}
90.子集 II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
去重就要排序,标记used[],再剪枝
class Solution {
List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
boolean[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
if (nums.length == 0){
result.add(path);
return result;
}
Arrays.sort(nums);
used = new boolean[nums.length];
subsetsWithDupHelper(nums, 0);
return result;
}
private void subsetsWithDupHelper(int[] nums, int startIndex){
result.add(new ArrayList<>(path));
if (startIndex >= nums.length){
return;
}
for (int i = startIndex; i < nums.length; i++){
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
continue;
}
path.add(nums[i]);
used[i] = true;
subsetsWithDupHelper(nums, i + 1);
path.removeLast();
used[i] = false;
}
}
}
作者:carlsun-2
链接:https://leetcode-cn.com/problems/subsets-ii/solution/90-zi-ji-iiche-di-li-jie-zi-ji-wen-ti-ru-djmf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。