利用boolean 数组进行标识。当然用hashmap也可以。
class Solution { private boolean[][] line = new boolean[9][9]; private boolean[][] column = new boolean[9][9]; private boolean[][][] block = new boolean[3][3][9]; private boolean valid = false; private List<int[]> spaces = new ArrayList<int[]>(); public void solveSudoku(char[][] board) { for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (board[i][j] == '.') { spaces.add(new int[]{i, j}); } else { int digit = board[i][j] - '0' - 1; line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true; } } } dfs(board, 0); } public void dfs(char[][] board, int pos) { if (pos == spaces.size()) { valid = true; return; } int[] space = spaces.get(pos); int i = space[0], j = space[1]; for (int digit = 0; digit < 9 && !valid; ++digit) { if (!line[i][digit] && !column[j][digit] && !block[i / 3][j / 3][digit]) { line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = true; board[i][j] = (char) (digit + '0' + 1); dfs(board, pos + 1); line[i][digit] = column[j][digit] = block[i / 3][j / 3][digit] = false; } } } }思路与算法
在方法一中,我们使用了长度为 9 的数组表示每个数字是否出现过。我们同样也可以借助位运算,仅使用一个整数表示每个数字是否出现过。
具体地,数 b 的二进制表示的第 i 位(从低到高,最低位为第 0 位)为 1,当且仅当数字 i+1 已经出现过。例如当 b 的二进制表示为 (011000100)_2时,就表示数字 3,7,8 已经出现过。
位运算有一些基础的使用技巧。下面列举了所有在代码中使用到的技巧:具体参考以下文章 https://leetcode-cn.com/problems/sudoku-solver/solution/jie-shu-du-by-leetcode-solution/ class Solution { private int[] line = new int[9]; private int[] column = new int[9]; private int[][] block = new int[3][3]; private boolean valid = false; private List<int[]> spaces = new ArrayList<int[]>(); public void solveSudoku(char[][] board) { for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (board[i][j] == '.') { spaces.add(new int[]{i, j}); } else { int digit = board[i][j] - '0' - 1; flip(i, j, digit); } } } dfs(board, 0); } public void dfs(char[][] board, int pos) { if (pos == spaces.size()) { valid = true; return; } int[] space = spaces.get(pos); int i = space[0], j = space[1]; int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff; for (; mask != 0 && !valid; mask &= (mask - 1)) { int digitMask = mask & (-mask); int digit = Integer.bitCount(digitMask - 1); flip(i, j, digit); board[i][j] = (char) (digit + '0' + 1); dfs(board, pos + 1); flip(i, j, digit); } } public void flip(int i, int j, int digit) { line[i] ^= (1 << digit); column[j] ^= (1 << digit); block[i / 3][j / 3] ^= (1 << digit); } } 转载: 作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/sudoku-solver/solution/jie-shu-du-by-leetcode-solution/这样一来,我们可以不断地对整个数独进行遍历,将可以唯一确定的空白格全部填入对应的数。随后我们再使用与方法二相同的方法对剩余无法唯一确定的空白格进行递归 + 回溯
class Solution { private int[] line = new int[9]; private int[] column = new int[9]; private int[][] block = new int[3][3]; private boolean valid = false; private List<int[]> spaces = new ArrayList<int[]>(); public void solveSudoku(char[][] board) { for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (board[i][j] != '.') { int digit = board[i][j] - '0' - 1; flip(i, j, digit); } } } while (true) { boolean modified = false; for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (board[i][j] == '.') { int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff; if ((mask & (mask - 1)) == 0) { int digit = Integer.bitCount(mask - 1); flip(i, j, digit); board[i][j] = (char) (digit + '0' + 1); modified = true; } } } } if (!modified) { break; } } for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (board[i][j] == '.') { spaces.add(new int[]{i, j}); } } } dfs(board, 0); } public void dfs(char[][] board, int pos) { if (pos == spaces.size()) { valid = true; return; } int[] space = spaces.get(pos); int i = space[0], j = space[1]; int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff; for (; mask != 0 && !valid; mask &= (mask - 1)) { int digitMask = mask & (-mask); int digit = Integer.bitCount(digitMask - 1); flip(i, j, digit); board[i][j] = (char) (digit + '0' + 1); dfs(board, pos + 1); flip(i, j, digit); } } public void flip(int i, int j, int digit) { line[i] ^= (1 << digit); column[j] ^= (1 << digit); block[i / 3][j / 3] ^= (1 << digit); } }经常玩数独的可能知道,在填数字的时候一般都是选择那些可填数字比少的位置来填,所以这里也可以使用这种方式。这里为了便于计算,稍微修改了一点,每行每列每个单元格都使用一个int数字来记录,因为int类型是32位,而数独的行和列以及单元格大小都是9,所以使用int来存储足够了,这里只使用int类型的低9位来存储。举个简单的例子,比如第一行已经填了3个数字,比如示例中的5,3,7。那么我们就可以使用一个int类型的数字来表示
//数独的长宽大小 final int N = 9; //行 private int[] rows = new int[N]; //列 private int[] cols = new int[N]; //单元格 private int[][] cells = new int[3][3]; public void solveSudoku(char[][] board) { //统计未填的个数 int count = 0; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { char ch = board[i][j]; if (ch == '.') { count++; } else { //如果已经有数字,把这个数字标记一下 fillNumber(i, j, ch - '1', true); } } } //上面是一些计算前的准备工作,从这里开始调用回溯算法 backtrace(board, count); } private boolean backtrace(char[][] board, int count) { //如果可填的位置为0,就是填完了,直接返回true if (count == 0) { return true; } //找到可选择数字比较少的位置 int[] pos = getMinOkMaskCountPos(board); int x = pos[0], y = pos[1]; //获取可选择数字比较少的位置的mask int mask = getMask(x, y); for (char c = '1'; c <= '9'; c++) { int index = c - '1'; //判断这个位置是否可以填字符c if (testMask(mask, index)) { //如果可填,就把字符c填入到这个位置中 fillNumber(x, y, index, true); board[x][y] = c; //如果成功直接返回 if (backtrace(board, count - 1)) return true; //否则,撤销上面的操作 board[x][y] = '.'; fillNumber(x, y, index, false); } } return false; } //如果fill是true就把对应位置的数字从右边数第(n+1)位变为1,如果fill为false就把 //对应位置的数字从右边数第(n+1)位变为0, private void fillNumber(int x, int y, int n, boolean fill) { if (fill) { int mask = 1 << n; rows[x] = rows[x] | mask; cols[y] = cols[y] | mask; cells[x / 3][y / 3] = cells[x / 3][y / 3] | mask; } else { int mask = ~(1 << n); rows[x] = rows[x] & mask; cols[y] = cols[y] & mask; cells[x / 3][y / 3] = cells[x / 3][y / 3] & mask; } } //当前位置的行,列,单元格进行与运算,运算的结果就是如果这个数字的 //后面9位哪一个位置是0,就表示这个位置可以填对应的数字 private int getMask(int x, int y) { return rows[x] | cols[y] | cells[x / 3][y / 3]; } //统计上面的方法有多少位置还可以填 private int getCount(int mask) { int count = 0; for (int i = 0; i < N; i++) { if ((mask & (1 << i)) == 0) count++; } return count; } //判断mask从右边数第(index+1)个位置是否可以填入数字, //注意这里的index是从0开始,如果是0,就表示判断右边第1位 //能不能填入数字 private boolean testMask(int mask, int index) { return (mask & (1 << index)) == 0; } //统计所有的单元格,判断哪个单元格内可填数字比较少,就返回哪个单元格的坐标 private int[] getMinOkMaskCountPos(char[][] board) { int[] res = new int[2]; int min = 10; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (board[i][j] == '.') { int mask = getMask(i, j); int count = getCount(mask); if (count < min) { min = count; res[0] = i; res[1] = j; } } } } return res; } 转载: 作者:sdwwld 链接:https://leetcode-cn.com/problems/sudoku-solver/solution/hui-su-jie-jue-by-sdwwld/总结:位运算太强了