DFS
Depth-First-Search,深度优先搜索。

传送门

解决一个回溯问题, 实际上就是一个决策树的遍历过程.
主要思考三个问题:

  1. 路径(已做出的选择)
  2. 选择(当前可做的选择)
  3. 结束条件(决策树的底部,无法再做选择)

代码框架

  1. resule = []
  2. def backtrack(路径,选择列表):
  3. if(满足结束条件)
  4. result.add(路径)
  5. return;
  6. for 选择 in 选择列表:
  7. 做选择
  8. backtrack(路径,选择列表)
  9. 撤销选择

核心就是 for 循环里面的递归, 递归 backtrack() 之前「做选择」, 递归后「撤销选择」.

全排列问题

给出数组 a, [1, 2, 3] , 求出所有的排列组合.
1. 画图
image.png
从根部遍历, 记录路径上的数字, 就是所有的排列.
每个节点都是决策树, 比如下面红点:

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

image.png

image.png

其实也是树的遍历, 只需要处理好选择列表和路径, 所以核心框架为:

  1. for 选择 in 选择列表:
  2. # 做选择
  3. 将该选择从选择列表移除
  4. 路径.add(选择)
  5. backtrack(路径, 选择列表)
  6. # 撤销选择
  7. 路径.remove(选择)
  8. 将该选择再加入选择列表

Java 代码如下:

  1. public static void main(String[] args) {
  2. //选择列表
  3. int[] a = new int[]{1, 2, 3};
  4. //路径
  5. List<Integer> track = new LinkedList<>();
  6. //结果
  7. List<List<Integer>> res = new ArrayList<>();
  8. backTrack(a, track,res);
  9. System.out.println(res);
  10. }
  11. static void backTrack(int[] nums, List<Integer> track, List<List<Integer>> res) {
  12. // 5.触发结束条件(路径大小和选择列表大小一致时,为选择列表都选完 )
  13. if (track.size() == nums.length) {
  14. res.add(new ArrayList<>(track));
  15. return;
  16. }
  17. for (int n : nums) {
  18. // 4.排除不合法选择->track记录下已作出的选择
  19. if (track.contains(n)) {
  20. continue;
  21. }
  22. // 1.做选择
  23. track.add(n);
  24. // 2.进入下一层决策树
  25. backTrack(nums, track, res);
  26. // 3.取消选择
  27. track.remove(track.size() - 1);
  28. }
  29. }

这里并无显式的记录『选择列表』, 而是用 LinkedList.contains() 方法判断元素是否已在『选择列表』。

时间复杂度:DFS - 图5
空间复杂度:DFS - 图6

排列

排列 DFS - 图7 ,且n >= m

公式:DFS - 图8

DFS - 图9

例: A(2,4) = 4*3 = 12

  1. /**
  2. * 给出数组 int[] a = new int[]{1, 2, 3, 4},
  3. * 排列组合元素个数 K
  4. * 列出所有排列可能性
  5. */
  6. public static void main(String[] args) {
  7. //选择列表
  8. int[] a = new int[]{1, 2, 3, 4};
  9. int k = 2;
  10. //路径
  11. List<Integer> track = new LinkedList<>();
  12. //结果
  13. List<List<Integer>> res = new ArrayList<>();
  14. backTrack(a, k, track, res);
  15. System.out.println(res.size());
  16. System.out.println(res);
  17. }
  18. static void backTrack(int[] nums, int k, List<Integer> track, List<List<Integer>> res) {
  19. // 5.触发结束条件
  20. if (track.size() == k) {
  21. res.add(new ArrayList<>(track));
  22. return;
  23. }
  24. for (int i = 0; i < nums.length; i++) {
  25. // 4.排除不合法选择->track记录下已作出的选择
  26. if (track.contains(nums[i])) {
  27. continue;
  28. }
  29. // 1.做选择
  30. track.add(nums[i]);
  31. // 2.进入下一层决策树
  32. backTrack(nums, k, track, res);
  33. // 3.取消选择
  34. track.remove(track.size() - 1);
  35. }
  36. /**
  37. * n为总数,k为筛选个数,A(m,n)
  38. */
  39. static void backTrack1(int n, int k, List<Integer> track, List<List<Integer>> res) {
  40. // 5.触发结束条件
  41. if (track.size() == k) {
  42. res.add(new ArrayList<>(track));
  43. return;
  44. }
  45. for (int i = 0; i < n; i++) {
  46. // 4.排除不合法选择->track记录下已作出的选择
  47. if (track.contains(i)) {
  48. continue;
  49. }
  50. // 1.做选择
  51. track.add(i);
  52. // 2.进入下一层决策树
  53. backTrack1(n, k, track, res);
  54. // 3.取消选择
  55. track.remove(track.size() - 1);
  56. }
  57. }
  58. }

组合

组合 DFS - 图10 ,且n >= m

公式:DFS - 图11
DFS - 图12

例: C(2, 4) = (43) / (21) = 6

  1. /**
  2. * 给出数组 int[] a = new int[]{1, 2, 3, 4};
  3. * 列出所有组合可能性
  4. *
  5. *
  6. * 此问题等同于 排列组合C(m,n)问题 C(2,4) = 6
  7. * 与排列不同的是,组合的数据无序,排列的数据有序 如(1,4)和(4,1)对于排列是两组数据,对于组合只是一组
  8. */
  9. public static void main(String[] args) {
  10. //选择列表
  11. int[] a = new int[]{1, 2, 3, 4};
  12. //路径
  13. List<Integer> track = new LinkedList<>();
  14. int k = 2;
  15. //结果
  16. List<List<Integer>> res = new ArrayList<>();
  17. backTrack(a, k, 0, track, res);
  18. System.out.println(res.size());
  19. System.out.println(res);
  20. }
  21. /**
  22. * 与上基本相同
  23. * n为总个数,k为筛选个数 C(k,n)
  24. */
  25. static void backTrack(int[] nums, int k, int start, List<Integer> track, List<List<Integer>> res) {
  26. // 5.触发结束条件
  27. if (track.size() == k) {
  28. res.add(new ArrayList<>(track));
  29. return;
  30. }
  31. for (int i = start; i < nums.length; i++) {
  32. // 4.排除不合法选择->track记录下已作出的选择
  33. if (track.contains(nums[i])) {
  34. continue;
  35. }
  36. // 1.做选择
  37. track.add(nums[i]);
  38. // 2.进入下一层决策树
  39. backTrack(nums, k, i, track, res);
  40. // 3.取消选择
  41. track.remove(track.size() - 1);
  42. }
  43. static void backTrack1(int n, int k, int start, List<Integer> track, List<List<Integer>> res) {
  44. // 5.触发结束条件
  45. if (track.size() == k) {
  46. res.add(new ArrayList<>(track));
  47. return;
  48. }
  49. for (int i = start; i < n; i++) {
  50. // 4.排除不合法选择->track记录下已作出的选择
  51. if (track.contains(i)) {
  52. continue;
  53. }
  54. // 1.做选择
  55. track.add(i);
  56. // 2.进入下一层决策树
  57. backTrack1(n, k, i, track, res);
  58. // 3.取消选择(回溯状态)
  59. track.remove(track.size() - 1);
  60. }
  61. }
  62. }