131. 分割回文串
給定一個字符串 s,將 s 分割成一些子串,使每個子串都是迴文串。
返回 s 所有可能的分割方案。
示例:
輸入: “aab”
輸出:
[
[“aa”,“b”],
[“a”,“a”,“b”]
]
回溯算法
class Solution {
private boolean checkPalindrome(String s, int begin, int end) {
while (begin < end) {
if (s.charAt(begin++) != s.charAt(end--)) {
return false;
}
}
return true;
}
private void backtracking(String s, List<List<String>> result, List<String> temp) {
if (s.length() == 0) {
result.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < s.length(); ++i) {
if (checkPalindrome(s, 0, i)) {
temp.add(s.substring(0, i + 1));
backtracking(s.substring(i + 1), result, temp);
temp.remove(temp.size() - 1);
}
}
}
public List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
backtracking(s, result, new ArrayList<>());
return result;
}
}
37. 解數獨
編寫一個程序,通過已填充的空格來解決數獨問題。
一個數獨的解法需遵循如下規則:
數字 1-9 在每一行只能出現一次。
數字 1-9 在每一列只能出現一次。
數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。
空白格用 ‘.’ 表示。
Note:
給定的數獨序列只包含數字 1-9 和字符 ‘.’ 。
你可以假設給定的數獨只有唯一解。
給定數獨永遠是 9x9 形式的。
回溯算法
詳細題解見https://leetcode-cn.com/problems/sudoku-solver/solution/jie-shu-du-by-leetcode/
class Solution {
private int n = 3;
private int N = n * n;
private int[][] rows = new int[N][N+1];
private int[][] cols = new int[N][N+1];
private int[][] boxes = new int[N][N+1];
private char[][] board;
private boolean solved = false;
private boolean checkPlace(int d, int row, int col) {
int index = (row / n) * n + col / n;
return rows[row][d] + cols[col][d] + boxes[index][d] == 0;
}
private void placeNumber(int d, int row, int col) {
int index = (row / n) * n + col / n;
rows[row][d]++;
cols[col][d]++;
boxes[index][d]++;
board[row][col] = (char)(d + '0');
}
private void removeNumber(int d, int row, int col) {
int index = (row / n) * n + col / n;
rows[row][d]--;
cols[col][d]--;
boxes[index][d]--;
board[row][col] = '.';
}
private void placeNextNumber(int row, int col) {
if (row == N - 1 && col == N - 1) {
solved = true;
} else {
if (col == N - 1) {
backtrack(row + 1, 0);
} else {
backtrack(row, col + 1);
}
}
}
private void backtrack(int row, int col) {
if (board[row][col] == '.') {
for (int d = 1; d <= N; d++) {
if (checkPlace(d, row, col)) {
placeNumber(d, row, col);
placeNextNumber(row, col);
if (!solved) {
removeNumber(d, row, col);
}
}
}
} else {
placeNextNumber(row, col);
}
}
public void solveSudoku(char[][] board) {
this.board = board;
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
char num = board[i][j];
if (num != '.') {
int d = Character.getNumericValue(num);
placeNumber(d, i, j);
}
}
}
backtrack(0, 0);
}
}
還有一種效率更高的方法,選擇的格子(’.’)在一行、一列和一個九宮格中可選數字最少的格子開始填數字。這樣能更快找到結果。
剪枝條件:我們應該選擇的格子('.')在一行、一列和一個九宮格中可選數字最少的格子開始填數字。
對於每行、每列和每個9宮格都可以用一個9位的2進制數字來標識該行(列,9宮格)那些數字可以填。
用1表示可填0表示不可填
如例題中第一行 :["5","3",".",".","7",".",".",".","."]
第一行中 有數字 5 3 7
下標 8 7 6 5 4 3 2 1 0
二進制數 1 1 0 1 0 1 0 1 1
因爲5 3 7 已經有了,所以第一行1 2 4 6 8 9可填
一共有9行所以用9個int表示行row[9],同理9列col[9],9個9宮格cell[3][3]
class Solution {
final int N = 9;
int[] row = new int [N], col = new int [N];
//ones數組表示0~2^9 - 1的整數中二進制表示中1的個數:如ones[7] = 3 ones[8] = 1
//map數組表示2的整數次冪中二進制1所在位置(從0開始) 如 map[1] = 0,map[2] = 1, map[4] = 2
int[] ones = new int[1 << N], map = new int[1 << N];
int[][] cell = new int[3][3];
public void solveSudoku(char[][] board) {
init();
int cnt = fill_state(board);
dfs(cnt, board);
}
void init(){
for(int i = 0; i < N; i++) row[i] = col[i] = (1 << N) - 1;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
cell[i][j] = (1 << N) - 1;
//以上2個循環把數組的數初始化爲二進制表示8個1,即一開始所以格子都可填
for(int i = 0; i < N; i++) map[1 << i] = i;
//統計0~2^9 - 1的整數中二進制表示中1的個數
for(int i = 0; i < 1 << N; i++){
int n = 0;
for(int j = i; j != 0; j ^= lowBit(j)) n++;
ones[i] = n;
}
}
int fill_state(char[][] board){
int cnt = 0; //統計board數組空格('.')的個數
for(int i = 0; i < N; i++){
for(int j = 0; j < N; j++){
if(board[i][j] != '.'){
int t = board[i][j] - '1';
//數獨中 i,j位置爲數組下標,修改row col cell數組中狀態
change_state(i, j, t);
}else cnt++;
}
}
return cnt;
}
boolean dfs(int cnt, char[][] board){
if(cnt == 0) return true;
int min = 10, x = 0, y = 0;
//剪枝,即找出當前所以空格可填數字個數最少的位置 記爲x y
for(int i = 0; i < N; i++){
for(int j = 0; j < N; j++){
if(board[i][j] == '.'){
int k = ones[get(i, j)];
if(k < min){
min = k;
x = i;
y = j;
}
}
}
}
//遍歷當前 x y所有可選數字
for(int i = get(x, y); i != 0; i ^= lowBit(i)){
int t = map[lowBit(i)];
change_state(x, y, t);
board[x][y] = (char)('1' + t);
if(dfs(cnt - 1, board)) return true;
//恢復現場
change_state(x, y, t);
board[x][y] = '.';
}
return false;
}
void change_state(int x, int y, int t){
row[x] ^= 1 << t;
col[y] ^= 1 << t;
cell[x / 3][y / 3] ^= 1 << t;
}
//對維護數組x行,y列的3個集合(行、列、九宮格)進行&運算
//就可以獲得可選數字的集合(因爲我們用1標識可填數字)
int get(int x, int y){
return row[x] & col[y] & cell[x / 3][y / 3];
}
int lowBit(int x){
return -x & x;
}
}
//作者:ri-mu-tu-yuan-12
51. N皇后
n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,並且使皇后彼此之間不能相互攻擊。
上圖爲 8 皇后問題的一種解法。
給定一個整數 n,返回所有不同的 n 皇后問題的解決方案。
每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 ‘Q’ 和 ‘.’ 分別代表了皇后和空位。
示例:
輸入: 4
輸出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解釋: 4 皇后問題存在兩個不同的解法。
回溯算法
在 n*n 的矩陣中擺放 n 個皇后,並且每個皇后不能在同一行,同一列,同一對角線上,求所有的 n 皇后的解。
一行一行地擺放,在確定一行中的那個皇后應該擺在哪一列時,需要用三個標記數組來確定某一列是否合法,這三個標記數組分別爲:列標記數組、45 度對角線標記數組和 135 度對角線標記數組。
45 度對角線標記數組的長度爲 2 * n - 1,通過下圖可以明確 (r, c) 的位置所在的數組下標爲 r + c。
135 度對角線標記數組的長度也是 2 * n - 1,(r, c) 的位置所在的數組下標爲 n - 1 - (r - c)。
class Solution {
private List<List<String>> result;
private char[][] nQueens;
private boolean[] colUsed;
private boolean[] diagonal1; //45度對角線
private boolean[] diagonal2; //135度對角線
private int n;
private void backtrack(int row) {
if (row == n) {
List<String> temp = new ArrayList<>();
for (char[] chars : nQueens) {
temp.add(new String(chars));
}
result.add(temp);
return;
}
for (int col = 0; col < n; col++) {
int index1 = col + row; //45度對角線索引
int index2 = n - 1 - (row - col); //135度對角線索引
if (colUsed[col] || diagonal1[index1] || diagonal2[index2]) {
continue;
}
nQueens[row][col] = 'Q';
colUsed[col] = diagonal1[index1] = diagonal2[index2] = true;
backtrack(row + 1);
colUsed[col] = diagonal1[index1] = diagonal2[index2] = false;
nQueens[row][col] = '.';
}
}
public List<List<String>> solveNQueens(int n) {
result = new ArrayList<>();
nQueens = new char[n][n];
for (int i = 0; i < n; ++i) {
Arrays.fill(nQueens[i], '.');
}
colUsed = new boolean[n];
diagonal1 = new boolean[2 * n - 1];
diagonal2 = new boolean[2 * n - 1];
this.n = n;
backtrack(0);
return result;
}
}