image.png

回溯算法与深度优先遍历

image.png

个人理解

image.png
T46 剪枝

搜索与遍历

image.png

与动态规划的区别

image.png

从全排列问题开始理解回溯算法

image.pngimage.png

设计状态变量

image.png

代码实现

image.png

  1. from typing import List
  2. class Solution:
  3. def permute(self, nums: List[int]) -> List[List[int]]:
  4. def dfs(nums, size, depth, path, used, res):
  5. if depth == size:
  6. res.append(path)
  7. return
  8. for i in range(size):
  9. if not used[i]:
  10. used[i] = True
  11. path.append(nums[i])
  12. dfs(nums, size, depth + 1, path, used, res)
  13. used[i] = False
  14. path.pop()
  15. size = len(nums)
  16. if len(nums) == 0:
  17. return []
  18. used = [False for _ in range(size)]
  19. res = []
  20. dfs(nums, size, 0, [], used, res)
  21. return res
  22. if __name__ == '__main__':
  23. nums = [1, 2, 3]
  24. solution = Solution()
  25. res = solution.permute(nums)
  26. print(res)

执行 main 方法以后输出如下:

  1. [[], [], [], [], [], []]

原因出现在递归终止条件这里:

  1. if depth == size:
  2. res.append(path)
  3. return

变量 path 所指向的列表 在深度优先遍历的过程中只有一份 ,深度优先遍历完成以后,回到了根结点,成为空列表。
在 Java 中,参数传递是 值传递,对象类型变量在传参的过程中,复制的是变量的地址。这些地址被添加到 res 变量,但实际上指向的是同一块内存地址,因此我们会看到 66 个空的列表对象。解决的方法很简单,在 res.add(path); 这里做一次拷贝即可。
修改的部分:

  1. if depth == size:
  2. res.append(path[:])
  3. return

此时再提交到「力扣」上就能得到通过了,完整代码请见下文「参考代码 2」。image.png

理解回溯

image.png

通过打印输出观察

参考代码 2

  1. import java.util.ArrayDeque;
  2. import java.util.ArrayList;
  3. import java.util.Deque;
  4. import java.util.List;
  5. public class Solution {
  6. public List<List<Integer>> permute(int[] nums) {
  7. int len = nums.length;
  8. // 使用一个动态数组保存所有可能的全排列
  9. List<List<Integer>> res = new ArrayList<>();
  10. if (len == 0) {
  11. return res;
  12. }
  13. boolean[] used = new boolean[len];
  14. Deque<Integer> path = new ArrayDeque<>(len);
  15. dfs(nums, len, 0, path, used, res);
  16. return res;
  17. }
  18. private void dfs(int[] nums, int len, int depth,
  19. Deque<Integer> path, boolean[] used,
  20. List<List<Integer>> res) {
  21. if (depth == len) {
  22. res.add(new ArrayList<>(path));
  23. return;
  24. }
  25. for (int i = 0; i < len; i++) {
  26. if (!used[i]) {
  27. path.addLast(nums[i]);
  28. used[i] = true;
  29. System.out.println(" 递归之前 => " + path);
  30. dfs(nums, len, depth + 1, path, used, res);
  31. used[i] = false;
  32. path.removeLast();
  33. System.out.println("递归之后 => " + path);
  34. }
  35. }
  36. }
  37. public static void main(String[] args) {
  38. int[] nums = {1, 2, 3};
  39. Solution solution = new Solution();
  40. List<List<Integer>> lists = solution.permute(nums);
  41. System.out.println(lists);
  42. }
  43. }

控制台输出:

  1. 递归之前 => [1]
  2. 递归之前 => [1, 2]
  3. 递归之前 => [1, 2, 3]
  4. 递归之后 => [1, 2]
  5. 递归之后 => [1]
  6. 递归之前 => [1, 3]
  7. 递归之前 => [1, 3, 2]
  8. 递归之后 => [1, 3]
  9. 递归之后 => [1]
  10. 递归之后 => []
  11. 递归之前 => [2]
  12. 递归之前 => [2, 1]
  13. 递归之前 => [2, 1, 3]
  14. 递归之后 => [2, 1]
  15. 递归之后 => [2]
  16. 递归之前 => [2, 3]
  17. 递归之前 => [2, 3, 1]
  18. 递归之后 => [2, 3]
  19. 递归之后 => [2]
  20. 递归之后 => []
  21. 递归之前 => [3]
  22. 递归之前 => [3, 1]
  23. 递归之前 => [3, 1, 2]
  24. 递归之后 => [3, 1]
  25. 递归之后 => [3]
  26. 递归之前 => [3, 2]
  27. 递归之前 => [3, 2, 1]
  28. 递归之后 => [3, 2]
  29. 递归之后 => [3]
  30. 递归之后 => []
  31. 输出 => [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

几点说明帮助理解「回溯算法」

image.png

  1. from typing import List
  2. class Solution:
  3. def permute(self, nums: List[int]) -> List[List[int]]:
  4. def dfs(nums, size, depth, path, state, res):
  5. if depth == size:
  6. res.append(path)
  7. return
  8. for i in range(size):
  9. if ((state >> i) & 1) == 0:
  10. dfs(nums, size, depth + 1, path + [nums[i]], state ^ (1 << i), res)
  11. size = len(nums)
  12. if size == 0:
  13. return []
  14. state = 0
  15. res = []
  16. dfs(nums, size, 0, [], state, res)
  17. return res

image.png
image.png

剪枝

image.png

总结

image.png