1.全排列II
题目链接
思路:回溯之排列问题并且有数组排序+标记数组。 回溯三部曲同46. 全排列。
过程图:https://programmercarl.com/0047.%E5%85%A8%E6%8E%92%E5%88%97II.html#%E6%80%9D%E8%B7%AF
注意:本题的去重操作主要在树层上。就是说同一树层上存在重复值时,需要进行去重,而且必然在第二层及以下层,同时需要使用标记数组对访问过的结点进行标记。
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
// 标记数组
boolean[] used = new boolean[nums.length];
// 设置默认值为false 并对nums排序
Arrays.fill(used, false);
Arrays.sort(nums);
back(nums,used);
return res;
}
private void back(int[] nums, boolean[] used) {
// 终止条件
if (nums.length == path.size()){
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++){
// 当访问的结点已经访问过时直接continue
if (used[i] == true)
continue;
// 当同一树层上存在重复值 used[i - 1] == false可以确保重复值仅访问一次
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false)
continue;
used[i] = true;
path.add(nums[i]);
back(nums, used);
used[i] = false; // 回溯
path.removeLast();
}
}
}
2.N皇后
题目链接
思路:回溯。
注意:1.皇后们的约束条件:不能同行,不能同列,不能同斜线
2.回溯三部曲
递归函数参数:
n是棋盘的大小,然后用row来记录当前遍历到棋盘的第几层,还有棋盘本身chessboard
递归终止条件:
只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了(边搜索边判断皇后的约束条件,若不符合约束条件,会直接跳出)。
单层搜索的逻辑:
for循环用来横向遍历; 递归用来纵向遍历。具体逻辑见下图。
3.验证棋盘是否合法isValid函数:
isValid(row, i, n, chessboard:row是行, i是列,n是棋盘大小)
4.代码里的函数说明:
函数array2:是为了将每一行的字符数组(如果n=4,则每一行的字符数组就是4个元素)转换为一个字符串,然后将以字符串为单位生成list<String>,添加到res中。(copyValueOf就是接受一个字符数组并返回一个字符串)
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
// chessboard 用来记录棋盘元素
char[][] chessboard = new char[n][n];
// 默认都是'.'
for (char[] c : chessboard) {
Arrays.fill(c, '.');
}
// n是棋盘大小 row来记录当前遍历到棋盘的第几层
back(n, 0, chessboard);
return res;
}
//
private void back(int n, int row, char[][] chessboard) {
if (row == n){
res.add(array2(chessboard));
return;
}
for (int i = 0; i < n; i++){
if (isValid(row, i, n, chessboard)){
chessboard[row][i] = 'Q';
back(n, row + 1, chessboard);
chessboard[row][i] = '.'; // 回溯
}
}
}
// 判断是否符合皇后的约束条件 row是行,col是列,n是棋盘大小
private boolean isValid(int row, int col, int n, char[][] chessboard) {
// 检查列
for (int i = 0; i < row; i++) {
if (chessboard[i][col] == 'Q')
return false;
}
// 检查45度角 (10点半方向)
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
if (chessboard[i][j] == 'Q')
return false;
}
// 检查135度角 (1点半方向)
for (int i = row - 1, j = col + 1; i>= 0 && j <= n - 1; i--, j++){
if (chessboard[i][j] == 'Q')
return false;
}
return true;
}
// 见注意
public List array2(char[][] chessboard){
List<String> list = new ArrayList<>();
for (char[] c : chessboard) {
list.add(String.copyValueOf(c));
}
return list;
}
3.解数独(难得很,希望面试抽不到)
题目链接
思路:判断棋盘是否合法有如下三个维度:
同行是否重复
同列是否重复
9宫格里是否重复
回溯三部曲:
递归函数以及参数:只要解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,所以用boolean,
递归终止条件:单层逻辑判断中,针对每一行的判断,返回true和false就是终止条件
单层逻辑:一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
class Solution {
public void solveSudoku(char[][] board) {
solveSudokuHelper(board);
}
private boolean solveSudokuHelper(char[][] board){
//「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,
// 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」
for (int i = 0; i < 9; i++){ // 遍历行
for (int j = 0; j < 9; j++){ // 遍历列
if (board[i][j] != '.'){ // 跳过原始数字
continue;
}
for (char k = '1'; k <= '9'; k++){ // (i, j) 这个位置放k是否合适
if (isValidSudoku(i, j, k, board)){
board[i][j] = k;
if (solveSudokuHelper(board)){ // 如果找到合适一组立刻返回
return true;
}
board[i][j] = '.';
}
}
// 9个数都试完了,都不行,那么就返回false
return false;
// 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
// 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
}
}
// 遍历完没有返回false,说明找到了合适棋盘位置了
return true;
}
/**
* 判断棋盘是否合法有如下三个维度:
* 同行是否重复
* 同列是否重复
* 9宫格里是否重复
*/
private boolean isValidSudoku(int row, int col, char val, char[][] board){
// 同行是否重复
for (int i = 0; i < 9; i++){
if (board[row][i] == val){
return false;
}
}
// 同列是否重复
for (int j = 0; j < 9; j++){
if (board[j][col] == val){
return false;
}
}
// 9宫格里是否重复
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++){
for (int j = startCol; j < startCol + 3; j++){
if (board[i][j] == val){
return false;
}
}
}
return true;
}
}