DFS
Depth-First-Search,深度优先搜索。
解决一个回溯问题, 实际上就是一个决策树的遍历过程.
主要思考三个问题:
- 路径(已做出的选择)
- 选择(当前可做的选择)
- 结束条件(决策树的底部,无法再做选择)
代码框架
resule = []def backtrack(路径,选择列表):if(满足结束条件)result.add(路径)return;for 选择 in 选择列表:做选择backtrack(路径,选择列表)撤销选择
核心就是 for 循环里面的递归, 递归 backtrack() 之前「做选择」, 递归后「撤销选择」.
全排列问题
给出数组 a, [1, 2, 3] , 求出所有的排列组合.
1. 画图
从根部遍历, 记录路径上的数字, 就是所有的排列.
每个节点都是决策树, 比如下面红点:

红点中可以做选择 1 或 3, 因为 2 已经是做出的选择, 成为了路径, 而结束条件就是树的底层.
如图:


其实也是树的遍历, 只需要处理好选择列表和路径, 所以核心框架为:
for 选择 in 选择列表:# 做选择将该选择从选择列表移除路径.add(选择)backtrack(路径, 选择列表)# 撤销选择路径.remove(选择)将该选择再加入选择列表
Java 代码如下:
public static void main(String[] args) {//选择列表int[] a = new int[]{1, 2, 3};//路径List<Integer> track = new LinkedList<>();//结果List<List<Integer>> res = new ArrayList<>();backTrack(a, track,res);System.out.println(res);}static void backTrack(int[] nums, List<Integer> track, List<List<Integer>> res) {// 5.触发结束条件(路径大小和选择列表大小一致时,为选择列表都选完 )if (track.size() == nums.length) {res.add(new ArrayList<>(track));return;}for (int n : nums) {// 4.排除不合法选择->track记录下已作出的选择if (track.contains(n)) {continue;}// 1.做选择track.add(n);// 2.进入下一层决策树backTrack(nums, track, res);// 3.取消选择track.remove(track.size() - 1);}}
这里并无显式的记录『选择列表』, 而是用 LinkedList.contains() 方法判断元素是否已在『选择列表』。
时间复杂度:
空间复杂度:
排列
排列 ,且n >= m
公式:
例: A(2,4) = 4*3 = 12
/*** 给出数组 int[] a = new int[]{1, 2, 3, 4},* 排列组合元素个数 K* 列出所有排列可能性*/public static void main(String[] args) {//选择列表int[] a = new int[]{1, 2, 3, 4};int k = 2;//路径List<Integer> track = new LinkedList<>();//结果List<List<Integer>> res = new ArrayList<>();backTrack(a, k, track, res);System.out.println(res.size());System.out.println(res);}static void backTrack(int[] nums, int k, List<Integer> track, List<List<Integer>> res) {// 5.触发结束条件if (track.size() == k) {res.add(new ArrayList<>(track));return;}for (int i = 0; i < nums.length; i++) {// 4.排除不合法选择->track记录下已作出的选择if (track.contains(nums[i])) {continue;}// 1.做选择track.add(nums[i]);// 2.进入下一层决策树backTrack(nums, k, track, res);// 3.取消选择track.remove(track.size() - 1);}/*** n为总数,k为筛选个数,A(m,n)*/static void backTrack1(int n, int k, List<Integer> track, List<List<Integer>> res) {// 5.触发结束条件if (track.size() == k) {res.add(new ArrayList<>(track));return;}for (int i = 0; i < n; i++) {// 4.排除不合法选择->track记录下已作出的选择if (track.contains(i)) {continue;}// 1.做选择track.add(i);// 2.进入下一层决策树backTrack1(n, k, track, res);// 3.取消选择track.remove(track.size() - 1);}}}
组合
组合 ,且n >= m
公式:
例: C(2, 4) = (43) / (21) = 6
/*** 给出数组 int[] a = new int[]{1, 2, 3, 4};* 列出所有组合可能性*** 此问题等同于 排列组合C(m,n)问题 C(2,4) = 6* 与排列不同的是,组合的数据无序,排列的数据有序 如(1,4)和(4,1)对于排列是两组数据,对于组合只是一组*/public static void main(String[] args) {//选择列表int[] a = new int[]{1, 2, 3, 4};//路径List<Integer> track = new LinkedList<>();int k = 2;//结果List<List<Integer>> res = new ArrayList<>();backTrack(a, k, 0, track, res);System.out.println(res.size());System.out.println(res);}/*** 与上基本相同* n为总个数,k为筛选个数 C(k,n)*/static void backTrack(int[] nums, int k, int start, List<Integer> track, List<List<Integer>> res) {// 5.触发结束条件if (track.size() == k) {res.add(new ArrayList<>(track));return;}for (int i = start; i < nums.length; i++) {// 4.排除不合法选择->track记录下已作出的选择if (track.contains(nums[i])) {continue;}// 1.做选择track.add(nums[i]);// 2.进入下一层决策树backTrack(nums, k, i, track, res);// 3.取消选择track.remove(track.size() - 1);}static void backTrack1(int n, int k, int start, List<Integer> track, List<List<Integer>> res) {// 5.触发结束条件if (track.size() == k) {res.add(new ArrayList<>(track));return;}for (int i = start; i < n; i++) {// 4.排除不合法选择->track记录下已作出的选择if (track.contains(i)) {continue;}// 1.做选择track.add(i);// 2.进入下一层决策树backTrack1(n, k, i, track, res);// 3.取消选择(回溯状态)track.remove(track.size() - 1);}}}
