1. 回溯简介

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。

回溯法的性能如何呢,这里要和大家说清楚了,虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法
因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。
image.png
回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

回溯模板:

  1. 可以将回溯看成一个N叉树。
  2. void backtracking(参数) {
  3. if (终止条件) {
  4. 存放结果;
  5. return;
  6. }
  7. for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
  8. 处理节点;
  9. backtracking(路径,选择列表); // 递归
  10. 回溯,撤销处理结果
  11. }
  12. }

2. 题目分类

image.png


3. 组合问题


77. 组合 中等

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。

示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

示例 2:
输入:n = 1, k = 1
输出:[[1]]

提示:
1 <= n <= 20
1 <= k <= n

题解概要:

1.组合问题是典型的回溯问题。相当于一个N叉树的遍历取值。其中,数组的宽度是N叉树的宽度,递归的深度是N叉树的深度。

代码:

  1. func combine(n int, k int) [][]int {
  2. ans := [][]int{}
  3. var dfs func(int,[]int)
  4. dfs = func(start int,path []int){
  5. if len(path) == k{
  6. tmp := make([]int,len(path))
  7. copy(tmp,path)
  8. ans = append(ans,tmp)
  9. return
  10. }
  11. //剪枝
  12. if len(path)+n-start+1 < k{
  13. return
  14. }
  15. for i := start;i <= n;i++{
  16. path = append(path,i)
  17. dfs(i+1,path)
  18. path = path[:len(path)-1]
  19. }
  20. }
  21. dfs(1,[]int{})
  22. return ans
  23. }

216. 组合总和 III 中等

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:
所有数字都是正整数。
解集不能包含重复的组合。

示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]

示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

题解概要:

1.组合问题是典型的回溯问题。相当于一个N叉树的遍历取值。其中,数组的宽度是N叉树的宽度,递归的深度是N叉树的深度。

代码:

  1. func combinationSum3(k int, n int) [][]int {
  2. ans := [][]int{}
  3. var dfs func(int,int,[]int)
  4. dfs = func(start int,sum int,path []int){
  5. if len(path) == k && sum == n{
  6. tmp := make([]int,len(path))
  7. copy(tmp,path)
  8. ans = append(ans,tmp)
  9. return
  10. }
  11. //剪枝
  12. if len(path)+n-start+1 < k || sum > n{
  13. return
  14. }
  15. for i := start;i<=9;i++{
  16. path = append(path,i)
  17. sum+=i
  18. dfs(i+1,sum,path)
  19. sum-=i
  20. path = path[:len(path)-1]
  21. }
  22. }
  23. dfs(1,0,[]int{})
  24. return ans
  25. }

17. 电话号码的字母组合 中等

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
回溯 - 图3

示例 1:
输入:digits = “23”
输出:[“ad”,”ae”,”af”,”bd”,”be”,”bf”,”cd”,”ce”,”cf”]

示例 2:
输入:digits = “”
输出:[]

示例 3:
输入:digits = “2”
输出:[“a”,”b”,”c”]

提示:
0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

题解概要:

1.这道题也是组合问题。但index代表的含义不同,且N叉树的宽度不再是数组宽度了,而是每个数字在键盘上对应的字母数量。

代码:

  1. func letterCombinations(digits string) []string {
  2. ans := []string{}
  3. n := len(digits)
  4. if n == 0{
  5. return ans
  6. }
  7. m := [10]string{
  8. "",
  9. "",
  10. "abc",
  11. "def",
  12. "ghi",
  13. "jkl",
  14. "mno",
  15. "pqrs",
  16. "tuv",
  17. "wxyz",
  18. }
  19. var dfs func(int,[]byte)
  20. dfs = func(index int,path []byte){
  21. //满足条件
  22. if index == n{
  23. tmp := make([]byte,len(path))
  24. copy(tmp,path)
  25. ans = append(ans,string(tmp))
  26. return
  27. }
  28. //找到当前数字对应的键盘字符数组
  29. letters := m[digits[index]-'0']
  30. //遍历字符数组
  31. for i := 0;i < len(letters);i++{
  32. path = append(path,letters[i])
  33. dfs(index+1,path)
  34. path = path[:len(path)-1]
  35. }
  36. }
  37. dfs(0,[]byte{})
  38. return ans
  39. }

39. 组合总和 中等

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。

示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:
输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 1 <= candidates[i] <= 200
  • candidate 中的每个元素都 互不相同
  • 1 <= target <= 500

题解概要:

1.这道题也是组合问题。由于可以重复选择元素,因此每次递归时,i不需要+1.

代码:

  1. func combinationSum(candidates []int, target int) [][]int {
  2. ans := [][]int{}
  3. var dfs func(int,int,[]int)
  4. dfs = func(start int,sum int,path []int){
  5. if sum > target{
  6. return
  7. }
  8. if sum == target{
  9. tmp := make([]int,len(path))
  10. copy(tmp,path)
  11. ans = append(ans,tmp)
  12. return
  13. }
  14. for i := start;i < len(candidates);i++{
  15. path = append(path,candidates[i])
  16. sum += candidates[i]
  17. dfs(i,sum,path)
  18. sum -= candidates[i]
  19. path = path[:len(path)-1]
  20. }
  21. }
  22. dfs(0,0,[]int{})
  23. return ans
  24. }

40. 组合总和 II 中等

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次
注意:解集不能包含重复的组合。

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出: [ [1,2,2], [5] ]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

题解概要:

1.这道题也是组合问题。但是要注意去重,去重的逻辑需梳理清楚。

代码:

  1. func combinationSum2(candidates []int, target int) [][]int {
  2. ans := [][]int{}
  3. sort.Ints(candidates)
  4. var dfs func(int,int,[]int)
  5. dfs = func(start int,sum int,path []int){
  6. if sum > target{
  7. return
  8. }
  9. if sum == target{
  10. tmp := make([]int,len(path))
  11. copy(tmp,path)
  12. ans = append(ans,tmp)
  13. return
  14. }
  15. for i := start;i < len(candidates);i++{
  16. //去重开始
  17. if i > start && candidates[i] == candidates[i-1]{
  18. continue
  19. }
  20. path = append(path,candidates[i])
  21. sum += candidates[i]
  22. dfs(i+1,sum,path) //由于每个数字都不能重复,因此i+1
  23. sum -= candidates[i]
  24. path = path[:len(path)-1]
  25. }
  26. }
  27. dfs(0,0,[]int{})
  28. return ans
  29. }

4.子集、排列问题


78. 子集 中等

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:
输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

题解概要:

1.子集问题其实是找N叉树上的所有节点。组合问题是找N叉树上的叶子节点。

代码:

  1. func subsets(nums []int) [][]int {
  2. ans := [][]int{}
  3. var backTracking func(int,[]int)
  4. backTracking = func(start int,path []int){
  5. //加入子集
  6. tmp := make([]int,len(path))
  7. copy(tmp,path)
  8. ans = append(ans,tmp)
  9. //终止条件
  10. if start == len(nums){
  11. return
  12. }
  13. //回溯
  14. for i := start;i < len(nums);i++{
  15. path = append(path,nums[i])
  16. backTracking(i+1,path)
  17. path = path[:len(path)-1]
  18. }
  19. }
  20. backTracking(0,[]int{})
  21. return ans
  22. }

90. 子集 II 中等

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:
输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10

题解概要:

1.子集问题其实是找N叉树上的所有节点。组合问题是找N叉树上的叶子节点。这道题要注意如何去重,和40题去重逻辑是一致的。

代码:

  1. func subsetsWithDup(nums []int) [][]int {
  2. ans := [][]int{}
  3. sort.Ints(nums)
  4. var backTracking func(int,[]int)
  5. backTracking = func(start int,path []int){
  6. //加入子集
  7. tmp := make([]int,len(path))
  8. copy(tmp,path)
  9. ans = append(ans,tmp)
  10. //终止条件
  11. if start == len(nums){
  12. return
  13. }
  14. //回溯
  15. for i := start;i < len(nums);i++{
  16. if i > start && nums[i] == nums[i-1]{
  17. continue
  18. }
  19. path = append(path,nums[i])
  20. backTracking(i+1,path)
  21. path = path[:len(path)-1]
  22. }
  23. }
  24. backTracking(0,[]int{})
  25. return ans
  26. }

491. 递增子序列 中等

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

示例 1:
输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

示例 2:
输入:nums = [4,4,3,2,1]
输出:[[4,4]]

提示:

  • 1 <= nums.length <= 15
  • -100 <= nums[i] <= 100

题解概要:

这道题和子集问题很像。但是去重逻辑不能和90题一样。可以在每一层都定义一个散列表来标明数字是否在同一层已被使用。
image.png

代码:

  1. func findSubsequences(nums []int) [][]int {
  2. ans := [][]int{}
  3. var backTracking func(int,[]int)
  4. backTracking = func(start int,path []int){
  5. if len(path) > 1{
  6. tmp := make([]int,len(path))
  7. copy(tmp,path)
  8. ans = append(ans,tmp)
  9. }
  10. used := [201]int{}
  11. for i := start;i < len(nums);i++{
  12. //同一树层去重逻辑。
  13. if (len(path) > 0 && nums[i] < path[len(path)-1]) || used[nums[i]+100] == 1{
  14. continue
  15. }
  16. path = append(path,nums[i])
  17. used[nums[i]+100] = 1
  18. backTracking(i+1,path)
  19. path = path[:len(path)-1]
  20. }
  21. }
  22. backTracking(0,[]int{})
  23. return ans
  24. }

46. 全排列 中等

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:
输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

题解概要:

1.子集问题其实是找N叉树上的所有节点。组合和排列问题是找N叉树上的叶子节点。但和组合不一样的是,排列中数是有序的,因此每次遍历时,都要从0开始,而不是从start开始。同时也需要用一个used数组标记数字是否被使用过。

代码:

  1. func permute(nums []int) [][]int {
  2. ans := [][]int{}
  3. used := make([]bool,len(nums))
  4. var backTracking func([]int,[]bool)
  5. backTracking = func(path []int,used []bool){
  6. if len(path) == len(nums){
  7. tmp := make([]int,len(path))
  8. copy(tmp,path)
  9. ans = append(ans,tmp)
  10. return
  11. }
  12. for i := 0;i < len(nums);i++{
  13. if used[i]{
  14. continue
  15. }
  16. path = append(path,nums[i])
  17. used[i] = true
  18. backTracking(path,used)
  19. path = path[:len(path)-1]
  20. used[i] = false
  21. }
  22. }
  23. backTracking([]int{},used)
  24. return ans
  25. }

47. 全排列 II 中等

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

示例 1:
输入:nums = [1,1,2]
输出: [[1,1,2], [1,2,1], [2,1,1]]

示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

题解概要:

1.子集问题其实是找N叉树上的所有节点。组合和排列问题是找N叉树上的叶子节点。但和组合不一样的是,排列中数是有序的,因此每次遍历时,都要从0开始,而不是从start开始。同时也需要用一个used数组标记数字是否被使用过。这里又涉及到去重,因此要将数组进行排序。
image.png
代码:

  1. func permuteUnique(nums []int) [][]int {
  2. ans := [][]int{}
  3. sort.Ints(nums)
  4. used := make([]bool,len(nums))
  5. var backTracking func([]int,[]bool)
  6. backTracking = func(path []int,used []bool){
  7. if len(path) == len(nums){
  8. tmp := make([]int,len(path))
  9. copy(tmp,path)
  10. ans = append(ans,tmp)
  11. return
  12. }
  13. for i := 0;i < len(nums);i++{
  14. //在同一树层上去重的逻辑
  15. if i > 0 && nums[i] == nums[i-1] && used[i-1] == false{
  16. continue
  17. }
  18. //used[i] = true,说明同一递归树上元素已经被使用。
  19. if used[i] == false{
  20. path = append(path,nums[i])
  21. used[i] = true
  22. backTracking(path,used)
  23. path = path[:len(path)-1]
  24. used[i] = false
  25. }
  26. }
  27. }
  28. backTracking([]int{},used)
  29. return ans
  30. }

5. 矩阵中的回溯


面试题 08.02. 迷路的机器人 中等

设想有个机器人坐在一个网格的左上角,网格 r 行 c 列。机器人只能向下或向右移动,但不能走到一些被禁止的网格(有障碍物)。设计一种算法,寻找机器人从左上角移动到右下角的路径。
回溯 - 图6
网格中的障碍物和空位置分别用 1 和 0 来表示。
返回一条可行的路径,路径由经过的网格的行号和列号组成。左上角为 0 行 0 列。如果没有可行的路径,返回空数组。

示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: [[0,0],[0,1],[0,2],[1,2],[2,2]]
解释:
输入中标粗的位置即为输出表示的路径,即
0行0列(左上角) -> 0行1列 -> 0行2列 -> 1行2列 -> 2行2列(右下角)
说明:r 和 c 的值均不超过 100。

题解概要:

1.使用回溯模板,这道题最重要的是进行剪枝,因为递归越深效率越低,使用剪枝可以大大提高效率。要标记格子是否被使用过。

代码:

  1. func pathWithObstacles(obstacleGrid [][]int) [][]int {
  2. ans := [][]int{}
  3. if len(obstacleGrid) == 0 || len(obstacleGrid[0]) == 0{
  4. return ans
  5. }
  6. n := len(obstacleGrid)
  7. m := len(obstacleGrid[0])
  8. if obstacleGrid[0][0] == 1 || obstacleGrid[n-1][m-1] == 1{
  9. return ans
  10. }
  11. var backTracking func(int,int,[][]int)
  12. backTracking = func(r int,c int,path [][]int){
  13. if r >= n || c >= m || obstacleGrid[r][c] != 0 || len(ans) != 0 {
  14. return
  15. }
  16. if r == n-1 && c == m-1{
  17. tmp := make([][]int,len(path))
  18. copy(tmp,path)
  19. ans = tmp
  20. return
  21. }
  22. obstacleGrid[r][c] = 2 //将访问过的格子标记一下,避免重复访问。
  23. //向右
  24. if c+1 < m && obstacleGrid[r][c+1] == 0{
  25. path = append(path,[]int{r,c+1})
  26. backTracking(r,c+1,path)
  27. path = path[:len(path)-1]
  28. }
  29. //向下
  30. if r+1 < n && obstacleGrid[r+1][c] == 0{
  31. path = append(path,[]int{r+1,c})
  32. backTracking(r+1,c,path)
  33. path = path[:len(path)-1]
  34. }
  35. }
  36. backTracking(0,0,[][]int{[]int{0,0}})
  37. return ans
  38. }

1219. 黄金矿工 中等

你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0。
为了使收益最大化,矿工需要按以下规则来开采黄金:

  • 每当矿工进入一个单元,就会收集该单元格中的所有黄金。
  • 矿工每次可以从当前位置向上下左右四个方向走。
  • 每个单元格只能被开采(进入)一次。
  • 不得开采(进入)黄金数目为 0 的单元格。
  • 矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。

示例 1:
输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释: [[0,6,0], [5,8,7], [0,9,0]] 一种收集最多黄金的路线是:9 -> 8 -> 7。

示例 2:
输入:grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]
输出:28
解释: [[1,0,7], [2,0,6], [3,4,5], [0,3,0], [9,0,20]] 一种收集最多黄金的路线是:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7。

提示:

  • 1 <= grid.length, grid[i].length <= 15
  • 0 <= grid[i][j] <= 100
  • 最多 25 个单元格中有黄金。

代码:

  1. var max int // 最终结果
  2. func getMaximumGold(grid [][]int) int {
  3. // 记录单元格是否被访问
  4. visited := make([][]bool, len(grid))
  5. for k := range visited {
  6. visited[k] = make([]bool, len(grid[0]))
  7. }
  8. max = 0
  9. // 以每一个有黄金的单元格为起点开始搜索
  10. for row, v := range grid {
  11. for col, v1 := range v {
  12. if v1 != 0 {
  13. backTrack(row, col, 0, grid, visited)
  14. }
  15. }
  16. }
  17. return max
  18. }
  19. // row/col 当前的行列 sum 当前的累加黄金 grid 金矿黄金分布 visited单元格访问数组
  20. func backTrack(row, col int, sum int, grid [][]int, visited [][]bool) {
  21. // 越界/无黄金/已访问 情况排除
  22. if row < 0 || row >= len(grid) || col < 0 || col >= len(grid[0]) || grid[row][col] == 0 || visited[row][col] {
  23. return
  24. }
  25. // 记得要把当前单元格置为已访问,否则会重复访问
  26. visited[row][col] = true
  27. sum += grid[row][col]
  28. if sum > max {
  29. max = sum
  30. }
  31. // 上下左右四个方向都要照顾到
  32. backTrack(row-1, col, sum, grid, visited)
  33. backTrack(row+1, col, sum, grid, visited)
  34. backTrack(row, col-1, sum, grid, visited)
  35. backTrack(row, col+1, sum, grid, visited)
  36. visited[row][col] = false //回溯
  37. }

179. 单词搜索 中等

回溯 - 图7
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:
输入:board = [[“A”,”B”,”C”,”E”],[“S”,”F”,”C”,”S”],[“A”,”D”,”E”,”E”]], word = “ABCCED”
输出:true
回溯 - 图8

示例 2:
输入:board = [[“A”,”B”,”C”,”E”],[“S”,”F”,”C”,”S”],[“A”,”D”,”E”,”E”]], word = “SEE”
输出:true

提示:
m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board 和 word 仅由大小写英文字母组成

题解概要:

1.使用剪枝和dfs要比回溯的效率更快。

代码:

  1. func exist(board [][]byte, word string) bool {
  2. m := len(board)
  3. n := len(board[0])
  4. used := make([][]bool,m)
  5. for i,_ := range used{
  6. used[i] = make([]bool,n)
  7. }
  8. var dfs func(int,int,int)bool
  9. //r代表矩阵的行,c代表矩阵的列,i代表word下标
  10. dfs = func(r,c,i int)bool{
  11. //判断是否找到单词
  12. if i == len(word){
  13. return true
  14. }
  15. //判断是否越界
  16. if r < 0 || r >= m || c < 0 || c >= n{
  17. return false
  18. }
  19. //判断元素是否合规
  20. if used[r][c] || board[r][c] != word[i]{
  21. return false
  22. }
  23. //将元素标为已用过
  24. used[r][c] = true
  25. //继续搜索
  26. canFind := dfs(r+1,c,i+1) || dfs(r,c+1,i+1) || dfs(r-1,c,i+1) || dfs(r,c-1,i+1)
  27. //判断是否已搜索到结果
  28. if canFind{
  29. return true
  30. }else{
  31. used[r][c] = false
  32. return false
  33. }
  34. }
  35. for i := 0;i < m; i++{
  36. for j := 0;j < n;j++{
  37. if board[i][j] == word[0] && dfs(i,j,0){
  38. return true
  39. }
  40. }
  41. }
  42. return false
  43. }

51. N 皇后 困难

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
回溯 - 图9
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

示例 1:
输入:n = 4
输出:[[“.Q..”,”…Q”,”Q…”,”..Q.”],[“..Q.”,”Q…”,”…Q”,”.Q..”]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:
输入:n = 1
输出:[[“Q”]]

提示:

  • 1 <= n <= 9

题解概要:

https://programmercarl.com/0051.N%E7%9A%87%E5%90%8E.html#%E6%80%9D%E8%B7%AF

代码:

  1. var ans [][]string
  2. func solveNQueens(n int) [][]string {
  3. ans = [][]string{}
  4. //初始化棋盘
  5. chessboard := make([][]string,n)
  6. for i := 0;i < n;i++{
  7. chessboard[i] = make([]string,n)
  8. for j := 0;j < n;j++{
  9. chessboard[i][j] = "."
  10. }
  11. }
  12. backTracking(0,n,chessboard)
  13. return ans
  14. }
  15. func backTracking(row int,n int,chessboard [][]string){
  16. //满足条件
  17. if row == n{
  18. tmp := make([]string,n)
  19. for i := 0;i < n;i++{
  20. tmp[i] = strings.Join(chessboard[i],"")
  21. }
  22. ans = append(ans,tmp)
  23. return
  24. }
  25. for i := 0;i < n;i++{
  26. if isValid(row,i,n,chessboard){
  27. chessboard[row][i] = "Q"
  28. backTracking(row+1,n,chessboard)
  29. chessboard[row][i] = "."
  30. }
  31. }
  32. }
  33. func isValid(row int,col int,n int,chessboard [][]string)bool{
  34. //判断列上是否已放置皇后。
  35. for i := row-1;i >= 0;i--{
  36. if chessboard[i][col] == "Q"{
  37. return false
  38. }
  39. }
  40. //判断45°角上是否已放置皇后
  41. i := row-1
  42. j := col-1
  43. for i >= 0 && j >= 0{
  44. if chessboard[i][j] == "Q"{
  45. return false
  46. }
  47. i--
  48. j--
  49. }
  50. //判断135°角上是否已放置皇后
  51. i = row-1
  52. j = col+1
  53. for i >= 0 && j < n{
  54. if chessboard[i][j] == "Q"{
  55. return false
  56. }
  57. i--
  58. j++
  59. }
  60. return true
  61. }

6. 其他


22. 括号生成 中等

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:
输入:n = 3
输出:[“((()))”,”(()())”,”(())()”,”()(())”,”()()()”]

示例 2:
输入:n = 1
输出:[“()”]

提示:

  • 1 <= n <= 8

题解概要:

1.重要的是画出N叉树。
回溯 - 图10
代码:

  1. func generateParenthesis(n int) []string {
  2. ans := []string{}
  3. var backTracking func(int,int,[]byte)
  4. backTracking = func(left,right int,path []byte){
  5. //是否符合条件
  6. if len(path) == n*2{
  7. tmp := make([]byte,len(path))
  8. copy(tmp,path)
  9. ans = append(ans,string(tmp))
  10. }
  11. //添加左括号
  12. if left < n{
  13. path = append(path,'(')
  14. backTracking(left+1,right,path)
  15. path = path[:len(path)-1]
  16. }
  17. //添加右括号
  18. if left > right{
  19. path = append(path,')')
  20. backTracking(left,right+1,path)
  21. path = path[:len(path)-1]
  22. }
  23. }
  24. backTracking(0,0,[]byte{})
  25. return ans
  26. }

131. 分割回文串 中等

给你一个字符串 s,请你将 _s _分割成一些子串,使每个子串都是回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。

示例 1:
输入:s = “aab”
输出:[[“a”,”a”,”b”],[“aa”,”b”]]

示例 2:
输入:s = “a”
输出:[[“a”]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

题解概要:

https://programmercarl.com/0131.%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2.html#%E5%9B%9E%E6%BA%AF%E4%B8%89%E9%83%A8%E6%9B%B2

例如对于字符串abcdef:

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中在选组第三个…..。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中在切割第三段…..。

所以切割问题,也可以抽象为一棵树形结构,如图:
回溯 - 图11
代码:

  1. func partition(s string) [][]string {
  2. ans := [][]string{}
  3. var backTracking func(int,[]string)
  4. backTracking = func(start int,path []string){
  5. if start >= len(s){
  6. tmp := make([]string,len(path))
  7. copy(tmp,path)
  8. ans = append(ans,tmp)
  9. return
  10. }
  11. for i := start;i < len(s);i++{
  12. //判断是否为回文串
  13. if !isValid(start,i,s){
  14. continue
  15. }
  16. path = append(path,s[start:i+1])
  17. backTracking(i+1,path) //进入下一层
  18. path = path[:len(path)-1]
  19. }
  20. }
  21. backTracking(0,[]string{})
  22. return ans
  23. }
  24. func isValid(l int,r int,s string)bool{
  25. for l < r{
  26. if s[l] != s[r]{
  27. return false
  28. }
  29. l ++
  30. r --
  31. }
  32. return true
  33. }