基本解法
回溯问题,其实就是一个决策树遍历。
要考虑三个问题。
- 路径:已做出的选择。
- 选择列表:当前能做的选择。
- 结束条件:到达决策树的叶子节点,不能选择时。
其实回溯问题可以理解为动规的暴力解法,而且是没有重叠子问题的动规。
result = []
public void backtrack(路径, 选择列表):
if 满足结束条件
result.add(路径);
return;
for 选择 in 选择列表
(排除不合法选择)
做选择
backtrack(路径, 选择列表);
撤销选择
全排列
给出n个数或字符,写出全部可能的排列组合。
理解为第i位上的字母和自己及其后面的位置上字母进行交换。
回溯就是在递归前做出选择,在递归后撤销之前的选择。
leetcode46题是无重复的数组,代码如下。
47题是有重复的数组,需要进行进一步剪枝。
- 每次加List时在Lists里判断是否有相同的元素 => 慢
- 用Set去重 => 快一点
- 先将数组排序,用一个boolen数组记录元素是否访问过。若nums[i]=nums[i-1],且nums[i-1]没有被访问,说明它俩在树的同一层;若nums[i]=nums[i-1],且nums[i-1]被使用过,说明i在i-1的子树中。这两种情况就剪枝。
代码实现
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
if (nums == null || nums.length == 0) {
return lists;
}
process(nums, 0, lists,new ArrayList<Integer>());
return lists;
}
public void process(int[] nums, int i, List<List<Integer>> lists, List<Integer> list) {
if (i == nums.length) {
for (int j = 0; j < nums.length; j++) {
list.add(nums[j]);
}
//其实可以不加这个判断
if (!lists.contains(new ArrayList<>(list))) {
lists.add(new ArrayList<>(list));
list.clear();
}
}
for (int j = i; j < nums.length; j++) {
swap(nums, i, j);
process(nums, i + 1, lists, list);
swap(nums, i, j);
}
}
public void swap(int[] nums, int a, int b) {
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
N皇后
N*N的棋盘,放N个皇后,让它们不能相互攻击。
注:皇后可以攻击同一行、同一列、左上左下右上右下四个方向。(理解为8个方向上的车)
- 路径:已选择的列表,即0-index行已经放了Q
- 选择:第index行的全部列都是可选择
- 结束:index超过最后一行
代码实现
List<List<String>> ret = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chars = new char[n][n];
//Arrays.fill(chars, '.');
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
chars[i][j] = '.';
}
}
process(n, 0, chars );
return ret;
}
public void process(int n, int index, char[][] chars ) {
if (index == n) {
List<String> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(new String(chars[i]));
}
ret.add(list);
return;
}
for (int col = 0; col < n; col++) {
//皇后就是八个方向的车,即八个方向上都不能有Q
//这里只要判断index的上半部分的方向
if (!isVaild(n, chars, index, col)) {
continue;
}
chars[index][col] = 'Q';
process(n, index + 1, chars);
chars[index][col] = '.';
}
}
//检查列,左上,右上,行有没有Q
public boolean isVaild(int n, char[][] chars, int row, int col) {
for (int i = 0; i < row; i++) {
if (chars[i][col] == 'Q') {
return false;
}
}
for (int i = 0; i < col; i++) {
if (chars[row][i] == 'Q') {
return false;
}
}
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (chars[i][j] == 'Q') {
return false;
}
}
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chars[i][j] == 'Q') {
return false;
}
}
return true;
}