模板

result = {}
void backtrack(选择列表, 路径) {
if (满足结束条件) {
result.add(路径)
return
} for 选择 in 选择列表 {
做选择
backtrack(选择列表,路径)
撤销选择
}
}

核心就是从选择列表里做一个选择,然后一直递归往下搜索答案,如果遇到路径不通,就返回来撤销这次选择。

推荐阅读回溯思想团灭排列、组合、子集问题

78. 子集

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

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

class Solution {
public List<list<integer>> subsets(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
dfs(nums, track, 0);
return res;
} private List<list<integer>> res = new LinkedList<>();
private void dfs (int[] nums, LinkedList<integer> track, int start) {
res.add(new LinkedList<>(track)); //通过start来控制当前可以选择的列表
for (int i = start; i < nums.length; i++) {
//选择
track.offerLast(nums[i]);
dfs(nums, track, i + 1);
//撤销选择
track.pollLast();
}
}
} // {1, 2, 3}对应递归树:
// []
// 1 2 3
// 12 13 23
// 123

90. 子集 II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

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

class Solution {
public List<list<integer>> subsetsWithDup(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
//先排序
Arrays.sort(nums);
dfs(nums, track, 0);
return res;
} private List<list<integer>> res = new LinkedList<>(); private void dfs(int[] nums, LinkedList<integer> track, int start) {
res.add(new LinkedList<>(track)); for (int i = start; i < nums.length; i++) {
//剪枝(跳过重复的)
if ((i != start) && (nums[i] == nums[i - 1])) {
continue;
} track.offerLast(nums[i]);
dfs(nums, track, i + 1);
track.pollLast();
}
}
} // {1, 2, 2, 3}对应递归树:
// []
// 1 2 2(剪) 3
// 12 12(剪) 13 22 23 23
// 123 123 223

46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

class Solution {
public List<list<integer>> permute(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
boolean[] visited = new boolean[nums.length]; dfs (nums, track, visited); return res;
} private List<list<integer>> res = new LinkedList<>();
private void dfs(int[] nums, LinkedList<integer> track, boolean[] visited) {
// base case
if (track.size() == nums.length) {
res.add(new LinkedList<integer>(track));
return;
} for (int i = 0; i < nums.length; i++) {
// track中已经包含的就跳过
if (visited[i]) {
continue;
} // 选择
track.offerLast(nums[i]);
visited[i] = true; dfs(nums, track, visited); // 撤销选择
track.pollLast();
visited[i] = false;
}
}
}

47. 全排列 II

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

class Solution {
public List<list<integer>> permuteUnique(int[] nums) {
LinkedList<integer> track = new LinkedList<>();
boolean[] visited = new boolean[nums.length];
Arrays.sort(nums); dfs (nums, track, visited); return res;
} private List<list<integer>> res = new LinkedList<>();
private void dfs(int[] nums, LinkedList<integer> track, boolean[] visited) {
// base case
if (track.size() == nums.length) {
res.add(new LinkedList<integer>(track));
return;
} for (int i = 0; i < nums.length; i++) {
// track中已经包含的就跳过
if (visited[i]) {
continue;
} // 剪枝
if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
continue;
} // 选择
track.offerLast(nums[i]);
visited[i] = true; dfs(nums, track, visited); // 撤销选择
track.pollLast();
visited[i] = false;
}
}
}

推荐阅读回溯搜索 + 剪枝

39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。
class Solution {
public List<list<integer>> combinationSum(int[] candidates, int target) {
LinkedList<integer> track = new LinkedList<>();
dfs(candidates, track, target, 0);
return res;
} private List<list<integer>> res = new LinkedList<>(); private void dfs(int[] candidates, LinkedList<integer> track, int target, int start) {
//base case
if (target < 0) {
return;
}
//base case
if (target == 0) {
res.add(new LinkedList<>(track));
return;
} //通过start改变可选列表
for (int i = start; i < candidates.length; i++) {
track.offerLast(candidates[i]);
dfs(candidates, track, target - candidates[i], i);
track.pollLast();
}
}
}

推荐阅读:回溯算法 + 剪枝(回溯经典例题详解)

class Solution {
public List<list<integer>> combinationSum(int[] candidates, int target) {
LinkedList<integer> track = new LinkedList<>();
Arrays.sort(candidates); //排序后可实现进一步剪枝
dfs(candidates, track, target, 0);
return res;
} private List<list<integer>> res = new LinkedList<>(); private void dfs(int[] candidates, LinkedList<integer> track, int target, int start) {
//不会再出现target小于0的情况
//base case
if (target == 0) {
res.add(new LinkedList<>(track));
return;
} //通过start改变可选列表
for (int i = start; i < candidates.length; i++) {
//由于此时candidates是有序的,candidates[i] 后面的元素都大于target,直接忽略
if (target - candidates[i] < 0) {
break;
} track.offerLast(candidates[i]);
dfs(candidates, track, target - candidates[i], i);
track.pollLast();
}
}
}

17. 电话号码的字母组合

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

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

class Solution {
public List<string> letterCombinations(String digits) {
if (digits == null || digits.length() <= 0) {
return new LinkedList<string>();
} StringBuilder track = new StringBuilder();
dfs(digits, 0, track);
return res;
} private Map<character, string[]=""> map = new HashMap<>() {
{
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"});
}
}; private List<string> res = new LinkedList<>();
private void dfs(String digits, int index, StringBuilder track) {
if (index == digits.length()) {
res.add(track.toString());
return;
} String[] alphabets = map.get(digits.charAt(index));
for (int i = 0; i < alphabets.length; i++) {
// 选择
track.append(alphabets[i]); dfs(digits, index + 1, track); // 撤销选择
track.deleteCharAt(track.length() - 1);
}
} }

131. 分割回文串

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

回文串 是正着读和反着读都一样的字符串。

class Solution {
public List<list<string>> partition(String s) { // 先使用动态规划获得任意两个区间的字符串是否为回文字符串
boolean[][] isPalindrome = getPalindrome(s); LinkedList<string> track = new LinkedList<>();
dfs(s, 0, track, isPalindrome); return res; } private List<list<string>> res = new LinkedList<>();
private boolean[][] getPalindrome(String s) {
int len = s.length(); // 区间i,j的字符串是否为回文字符串(左右都为闭区间)
boolean[][] dp = new boolean[len][len]; for (int j = 0; j < len; j++) {
for (int i = 0; i <= j; i++) {
if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
}
}
} return dp;
} private void dfs(String s, int start, LinkedList<string> track, boolean[][] isPalindrome) {
if (start == s.length()) {
res.add(new LinkedList<>(track));
return;
} for (int i = start; i < s.length(); i++) {
// 如果区间[start, i]的字符串不为回文字符串就可以剪枝
if (!isPalindrome[start][i]) {
continue;
} // 选择
track.offerLast(s.substring(start, i + 1)); dfs(s, i + 1, track, isPalindrome); // 撤销选择
track.pollLast();
}
}
}

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 地址。

class Solution {
public List<string> restoreIpAddresses(String s) {
int len = s.length();
if (len < 4 || len > 12) return new LinkedList<>(); LinkedList<string> track = new LinkedList<>();
dfs(s, 0 ,track); return res;
} private List<string> res = new LinkedList<>();
private void dfs(String s, int start, LinkedList<string> track) {
// 只有track已经有4段,且没有剩余字符时符合条件
if (track.size() == 4) {
if (start == s.length()) {
res.add(toStr(track));
} return;
} for (int i = start; i < start + 3 && i < s.length(); i++) {
String buf = s.substring(start, i + 1); if (!isValid(buf)) {
continue;
} track.offerLast(buf); dfs(s, i + 1, track); track.pollLast();
}
} private String toStr(LinkedList<string> list) {
return String.join(".", list);
} private boolean isValid(String str) {
int len = str.length();
int buf = Integer.parseInt(str); // 排除 09 等
if (len == 2 && buf < 10) {
return false;
// 排除 099、256 等
} else if (len == 3 && (buf < 100 || buf > 255)) {
return false;
} return true;
}
}

LeetCode入门指南 之 回溯思想的更多相关文章

  1. LeetCode入门指南 之 动态规划思想

    推荐学习labuladong大佬的动态规划系列文章:先弄明白什么是动态规划即可,不必一次看完.接着尝试自己做,没有思路了再回过头看相应的文章. 动态规划一般可以由 递归 + 备忘录 一步步转换而来,不 ...

  2. LeetCode入门指南 之 链表

    83. 删除排序链表中的重复元素 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 .返回同样按升序排列的结果链表. class Soluti ...

  3. LeetCode入门指南 之 排序

    912. 排序数组 给你一个整数数组 nums,请你将该数组升序排列. 归并排序 public class Sort { //归并排序 public static int[] MergeSort(in ...

  4. LeetCode入门指南 之 二叉树

    二叉树的遍历 递归: void traverse (TreeNode root) { if (root == null) { return null; } //前序遍历位置 traverse(root ...

  5. LeetCode入门指南 之 栈和队列

    栈 155. 最小栈 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x 推入栈中. pop() -- 删除栈顶的元素. top( ...

  6. LeetCode入门指南 之 二分搜索

    上图表示常用的二分查找模板: 第一种是最基础的,查找区间左右都为闭区间,比较后若不等,剩余区间都不会再包含mid:一般在不需要确定目标值的边界时,用此法即可. 第二种查找区间为左闭右开,要确定targ ...

  7. Ext JS 6学习文档–第1章–ExtJS入门指南

    Ext JS 入门指南 前言 本来我是打算自己写一个系列的 ExtJS 6 学习笔记的,因为 ExtJS 6 目前的中文学习资料还很少.google 搜索资料时找到了一本国外牛人写的关于 ExtJS ...

  8. 【翻译Autofac的帮助文档】1.入门指南

    [写在前面]尝试做完一件工作之外自我觉得有意义的一件事,那就从翻译Autofac的帮助文档吧. 入门指南 将Autofac集成你的应用程序的步骤通常很简单,一般是: 时刻以IOC(控制反转)的思想来规 ...

  9. OpenCASCADE入门指南

    OpenCASCADE入门指南 eryar@163.com 一.概述 荀子说“君子性非异也,善假于物也”.当你会用英语,就可以与世界各国的人交流:当你会用编程语言,就可以与计算机交流:当你会用数学语言 ...

随机推荐

  1. 解决 Github 打不开或打开很慢的问题

    解决 Github 打不开或打开很慢的问题 方法一 一.确定 github 网站的 ip 打开网址:http://github.com.ipaddress.com/ 192.30.253.112 gi ...

  2. Cent OS 7 本地yum源配置与安装

    一.本地yum源 1.添加一个新的yum源配置文件dvd.repo(文件名字自定义)  vi etc/yum.repos.d     添加新的内容: name=rhel_dvd            ...

  3. 【论文集合】机器翻译NMT中数据打分和数据选择的经典方法

    根据Survey of Data-Selection Methods in Statistical Machine Translation的总结,MT中的数据选择分类图如下: 使用场景 数据使用的场景 ...

  4. Capture One 21 Pro 14.0.2.36中文版安装教程

    Capture One 21 Pro 14.0.2.36 for windows是一款由丹麦PHASE ONE飞思公司推出的.非常专业强大的图像处理编辑工具. 下载 Capture One 21 Pr ...

  5. java小程序之随机任务发布器

    需求 自定义任务 随机定时 随机排序 集合输出 搭建 简单项目,所以我使用java 的maven来搭建一个控制台程序,并且使用excel来记录任务 创建任务类(sub) package com.mic ...

  6. 背单词(AC自动机+线段树+dp+dfs序)

    G. 背单词 内存限制:256 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较   题目描述 给定一张包含N个单词的表,每个单词有个价值W.要求从中选出一个子序列使 ...

  7. RHCSA_DAY09

    常用特殊符号的使用 Linux系统下通配符起到了很大的作用,对于不确定的文档名称可以使用以下特殊字符表示 *常用的特殊符号,在文件名上,用来代表任意多个任意字符** ? 常用的特殊符号,在文件名上,用 ...

  8. C++ //继承同名成员处理方式

    1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 class Base 6 { 7 pu ...

  9. 线程的常用知识(包括 Thread/Executor/Lock-free/阻塞/并发/锁等)

    本次内容列表: 1.使用线程的经验:设置名称.响应中断.使用ThreadLocal 2.Executor:ExecutorService和Future 3.阻塞队列:put和take.offer和po ...

  10. JavaScript学习04(标准对象)

    标准对象 在JavaScript的世界里,一切都是对象. 但是某些对象还是和其他对象不太一样.为了区分对象的类型,我们用 typeof 操作符获取对象的类型,它总是返回一个字符串: typeof 12 ...