回溯法
回溯法又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”
回溯是一种算法思想,可以用递归实现. 通俗点讲回溯就是一种试探.
在回溯法中,每次扩大当前部分解时,都面临一个可选的状态集合,新的部分解就通过在该集合中选择构造而成。这样的状态集合,其结构是一棵多叉树,每个树结点代表一个可能的部分解,它的儿子是在它的基础上生成的其他部分解。树根为初始状态,这样的状态集合称为状态空间树。
回溯算法的思想:
不断尝试, 直到不能尝试为止, 回退到上一步, 继续尝试.其实是一个深度优先遍历的过程
回溯法的效率
回溯算法本质上还是穷举.穷举所有可能,然后选出想要的答案.
可以通过剪枝操作,提高效率
效率低,为什么还选择使用回溯?因为一些问题只能用暴力搜索,没有更高效的解法
回溯解决的问题
- 组合问题
- 切割问题
- 子集问题
- 排列问题
- 棋盘问题
- 其他问题
Leetcode
组合
var path []int
var res [][]int
func main() {
n, k := 1, 1
res := combine(n, k)
fmt.Println(res)
}
func combine(n int, k int) [][]int {
res = make([][]int, 0)
backTracking(n, k, 0)
return res
}
func backTracking(n, k, startIndex int) {
if len(path) == k {
comb := make([]int, k)
copy(comb, path)
res = append(res, comb)
return
}
for i := startIndex; i < n; i++ {
path = append(path, i + 1)
backTracking(n, k, i + 1)
path = path[:len(path) - 1]
}
}
Java
class Solution {
List<List<Integer>> result;
List<Integer> path;
public List<List<Integer>> combine(int n, int k) {
path = new LinkedList<>();
result = new LinkedList<>();
backTracking(n, k, 0);
return result;
}
private void backTracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.add(new LinkedList<>(path));
return;
}
for (int i = startIndex; i < n; i ++){
path.add(i + 1);
backTracking(n, k, i + 1);
path.remove(path.size() - 1);
}
}
}
电话号码的字母组合
var result []string
var mp map[byte]string
func main() {
s := letterCombinations("23")
fmt.Println(s)
}
func letterCombinations(digits string) []string {
result = make([]string, 0)
if len(digits) == 0 {
return result
}
path := ""
mp = map[byte]string{
'2': "abc",
'3': "def",
'4': "ghi",
'5': "jkl",
'6': "mno",
'7': "pqrs",
'8': "tuv",
'9': "wxyz",
}
backTrack(digits, 0, path)
return result
}
func backTrack(digits string, depth int, path string) {
if len(digits) == len(path) {
result = append(result, path)
return
}
letter := mp[digits[depth]]
for i := 0; i < len(letter); i++ {
backTrack(digits, depth+1, path+string(letter[i]))
}
}
class Solution {
private Map<Character, String> mp;
private final List<String> result = new LinkedList<>();
private String digits;
private void initMap() {
this.mp = new HashMap<>();
mp.put('2', "abc");
mp.put('3', "def");
mp.put('4', "ghi");
mp.put('5', "jkl");
mp.put('6', "mno");
mp.put('7', "pqrs");
mp.put('8', "tuv");
mp.put('9', "wxyz");
}
public List<String> letterCombinations(String digits) {
String combination = "";
this.digits = digits;
if (digits.length() == 0) {
return result;
}
initMap();
backTracking(0, combination);
return result;
}
private void backTracking(int depth, String combination) {
if (combination.length() == digits.length()) {
result.add(combination.toString());
return;
}
String letter = mp.get(digits.charAt(depth));
for (int i = 0; i < letter.length(); i++) {
backTracking(depth + 1, combination + letter.charAt(i));
}
}
}
N皇后
func solveNQueens(n int) (result [][]string) {
tmpResult := [][]int{
}
//我这里用uesd 维护棋盘位置是否可选的状态
used := make([][]bool, n)
for i := 0; i < n; i++ {
used[i] = make([]bool, n)
}
path := []int{
}
var backTracking func([][]bool, int)
backTracking = func(used [][]bool, index int) {
if index > n {
return
}
if len(path) == n {
tmp := make([]int, n)
copy(tmp, path)
tmpResult = append(tmpResult, tmp)
return
}
for i := 0; i < n; i++ {
if !used[index][i] {
path = append(path, i)
// 为回溯做准备
tmp := make([][]bool, n)
for i := 0; i < n; i++ {
tmp[i] = make([]bool, n)
copy(tmp[i], used[i])
}
changeState(used, index, i)
backTracking(used, index+1)
// 回溯
used = tmp
path = path[:len(path)-1]
}
}
}
backTracking(used, 0)
result = make([][]string, len(tmpResult))
for i := 0; i < len(tmpResult); i++ {
result[i] = make([]string, n)
for j := 0; j < len(tmpResult[i]); j++ {
for k := 0; k < n; k++ {
if k == tmpResult[i][j] {
result[i][j] += "Q"
} else {
result[i][j] += "."
}
}
}
}
return
}
func changeState(used [][]bool, x, y int) {
b := y - x
b1 := x + y
for i := 0; i < len(used); i++ {
for j := 0; j < len(used[i]); j++ {
if j == y {
used[i][j] = true
} else if j-i == b || i+j == b1 {
used[i][j] = true
}
}
}
}
全排列
golang
func permute(nums []int) (result [][]int) {
path := []int{
}
//用used切片维护可选情况
used := make([]bool,len(nums))
var backTracking func([]int)
backTracking = func(c []int) {
if len(path) == len(nums) {
comb := make([]int, len(nums))
copy(comb, path)
result = append(result, comb)
return
}
for i := 0; i < len(c); i++ {
if !used[i] {
path = append(path, c[i])
used[i] = true
backTracking(nums)
path = path[:len(path)-1]
used[i] = false
}
}
}
backTracking(nums)
return
}
Java
class Solution {
private List<List<Integer>> result;
private List<Integer> path;
public List<List<Integer>> permute(int[] nums) {
result = new LinkedList<>();
path = new LinkedList<>();
boolean[] used = new boolean[nums.length];
backTracking(nums,used);
return result;
}
private void backTracking(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
this.result.add(new LinkedList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
path.add(nums[i]);
used[i] = true;
backTracking(nums,used);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
}