Back Track
回溯算法的框架就是DFS遍历,同时在遍历到叶的时候,判断是否符合条件,如果符合,保存下来
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
全排列
这个题其实就是回溯算法的基本框架
List<List<Integer>> res = new LinkedList<>();
List<List<Integer>> permute(int[] nums) { /* 主函数,输入一组不重复的数字,返回它们的全排列 */
LinkedList<Integer> track = new LinkedList<>(); // 记录「路径」
boolean[] used = new boolean[nums.length]; // 「路径」中的元素会被标记为true,避免重复使用
backtrack(nums, track, used);
return res;
}
// 路径:记录在 track 中
// 选择列表:nums 中还没用过的元素(used[i] 为 false)
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track, boolean[] used) {
if (track.size() == nums.length) { // 触发结束条件,就是路径已经与所有的元素一样长,完全排好了
res.add(new LinkedList(track));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) { // nums[i] 已经在 track 中,跳过(比如1,2,下一个就不能是1或者2了)
continue;
}
track.add(nums[i]);// 做选择, 前序
used[i] = true;
backtrack(nums, track, used);// 进入下一层决策树
track.removeLast();// 当前节点的子节点遍历完成,返回到上一层,取消选择,后序
used[i] = false;
}
}
n皇后
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
List<String> originalboard = new ArrayList<>(); //create a cheess board
for(int i = 0; i < n; i++){
originalboard.add(".".repeat(n)); // . means no queen
}
backtrack(originalboard, 0); //从第0行开始
return res;
}
// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(List<String> board, int row) {
if (row == board.size()) {// 触发结束条件
res.add(new ArrayList<>(board)); //添加到结果集里
return;
}
int n = board.get(row).length();
for (int col = 0; col < n; col++) { // 当前这一行每一个位置都判断一下
if (!isValid(board, row, col)) {// 首先排除不合法选择
continue;
}
// 做选择
char[] rowChar = board.get(row).toCharArray();
rowChar[col] = 'Q';
board.set(row, String.valueOf(rowChar));
backtrack(board, row + 1);// 进入下一行决策,
// 比如:当前一行为。。q 。的时候,看看下一行能不能凑出来,一直到全都凑出来为止,相当于树结构
// 撤销选择
rowChar = board.get(row).toCharArray();
rowChar[col] = '.';
board.set(row, String.valueOf(rowChar));
}
}
boolean isValid(List<String> board, int row, int col) {/* 是否可以在 board[row][col] 放置皇后?*/
int n = board.size();
for (int i = 0; i <= row; i++) {// 检查列是否有皇后互相冲突
if (board.get(i).charAt(col) == 'Q')
return false;
}
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {// 检查右上方是否有皇后互相冲突
if (board.get(i).charAt(j) == 'Q')
return false;
}
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {// 检查左上方是否有皇后互相冲突
if (board.get(i).charAt(j) == 'Q')
return false;
}
return true;
}
37. 解数独 - 力扣(LeetCode)
public void solveSudoku(char[][] board) {
backtrack(board, 0, 0);
}
boolean backtrack(char[][]board, int i, int j){
//顺序很重要,先判断是不是出界了,不然判断有没有值会indexoutofbound
if (j == 9) { // 穷举到最后一列的话就换到下一行重新开始。
return backtrack(board, i + 1, 0);
}
if (i == 9) { // 找到一个可行解,触发 base case
return true;
}
if(board[i][j] != '.'){
return backtrack(board, i, j + 1); //已经有值,不必穷尽,回溯下一个
}
for (char ch = '1'; ch <= '9'; ch++) {
if (!isValid(board, i, j, ch))// 如果遇到不合法的数字,就跳过
continue;
board[i][j] = ch; //选择
if (backtrack(board, i, j + 1)) {// 如果找到一个可行解,立即结束
return true;
}
board[i][j] = '.'; //撤销选择
}
return false;// 穷举完 1~9,依然没有找到可行解,此路不通
}
boolean isValid(char[][] board, int r, int c, char n) {// 判断 board[i][j] 是否可以填入 n
for (int i = 0; i < 9; i++) {
if (board[r][i] == n) return false;// 判断行是否存在重复
if (board[i][c] == n) return false;// 判断列是否存在重复
if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n)// 判断 3 x 3 方框是否存在重复
return false;
}
return true;
}
22. 括号生成 - 力扣(LeetCode)
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
if (n == 0) return result;
String track = "";
backtrack(track, n, n, result);
return result;
}
void backtrack(String track, int left, int right, List<String>result){
if(right < left || right<0 || left<0){ // error case
return;
}
if (left == 0 && right == 0){
result.add(track);
return;
}
track = track + "(";
backtrack(track, left - 1, right, result);
track = track.substring(0, track.length()-1);
track = track + ")";
backtrack(track, left, right - 1, result);
track = track.substring(0, track.length()-1);
}