LeetCode題解--回溯算法(四)

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行所以用9int表示行row[9],同理9列col[9],99宮格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;
    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章