题目:
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示。
一个数独。
答案被标成红色。
Note:
给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sudoku-solver
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:
- 采用回溯解决,注意剪掉不必要的分支,提高效率;从前往后再空格中填数字(满足要求的数字),如果顺利填完就算完成,填不下去了就回溯;
代码分析:
- 通过boolean数组记录每一行(rowRecord)、每一列(colRecord)、每个区域(regionRecord)的数字填写情况,如第二行第三列填写了一个9,则:rowRecord[2][9] = true来表明,第二行的数字9已经存在了,同样的更新列和区域的数字填写情况;这样可以达到O(1)的时间复杂度下判断某个位置是否能够填写某个数字;(其实这种空间换时间的做法极大的增加了代码的复杂度,不记录,直接按照行、列、区域不能重复来判断数字是否适合填在某个位置也是可以的,只不过时间复杂度是O(n));
- 遍历整个二维数组,将已经填好的数字的标志填到rowRecord、colRecord以及regionRecord中;
- 遍历这个二维数组,如果是数字,则是已经填好的数字,不用管,直接下一个,如果是’.’,则需要填数字,遍历1-9,能符合要求,就填写该数字,然后继续下一个空格的填写,如果最终填完返回true则程序结束,如果到某个点所有数字都不符合要求,则回溯;
代码实现:
class Solution {
public void solveSudoku(char[][] board) {
if(board == null || board.length < 1) return;
/**
* colRecord : 记录0-8列中,每个数字是否已经填过了;
* rowRecord : 同colRecord,不过记录的是行;
* regionRecord : 同colRecord,记录的是区域中,数字的填写情况;
*/
boolean[][] colRecord = new boolean[9][];
boolean[][] rowRecord = new boolean[9][];
boolean[][] regionRecord = new boolean[9][];
for(int i = 0 ; i < 9;++i){
colRecord[i] = new boolean[9];
rowRecord[i] = new boolean[9];
regionRecord[i] = new boolean[9];
}
//将已经事先填好的数字进行标记;
initInvalidRecord(board,colRecord,rowRecord,regionRecord);
//回溯解决问题
solve(board,0,0,colRecord,rowRecord,regionRecord);
}
private void initInvalidRecord(char[][] board,boolean[][] colRecord,boolean[][] rowRecord,boolean[][] regionRecord){
for(int i = 0 ;i < board.length;++i){
for(int j = 0; j < board[0].length;++j){
if(board[i][j] != '.'){
int number = board[i][j] - '1';
colRecord[j][number] = true;
rowRecord[i][number] = true;
regionRecord[i / 3 * 3 + j / 3][number] = true;
}
}
}
}
public boolean solve(char[][] board,int row,int col,boolean[][] colRecord,boolean[][] rowRecord,boolean[][] regionRecord){
if(row >= board.length || col >= board[0].length){ //成功填写所有数字
return true;
}
if(board[row][col] == '.'){ //需要填写数字
for(char c = '1'; c - '1' < 9; ++c){
if(isValid(c,row,col,colRecord,rowRecord,regionRecord)){
board[row][col] = c;
setInvalid(board,row,col,colRecord,rowRecord,regionRecord);
++col;
//下面的三目运算符实际上就是看列是否溢出,如溢出,则col=0,row = row+1;
int rowTemp = col >= board[0].length ? row+1 : row;
int colTemp = col >= board[0].length? 0: col;
if(solve(board,rowTemp ,colTemp,colRecord,rowRecord,regionRecord)){
return true;
} else { //问题没解决,需要回溯,还原状态;
--col;
setValid(board,row,col,colRecord,rowRecord,regionRecord);
board[row][col] = '.';
}
}
}
return false;
}else{//已预先填好数字,填写下一个
++col;
//下面的三目运算符实际上就是看列是否溢出,如溢出,则col=0,row = row+1;
if(solve(board,col >= board[0].length ? row+1 : row,col >= board[0].length? 0: col,colRecord,rowRecord,regionRecord)){
return true;
} else {
return false;
}
}
}
public void setValid(char[][] board,int row,int col,boolean[][] colRecord,boolean[][] rowRecord,boolean[][] regionRecord){
int number = board[row][col] - '1';
colRecord[col][number] = false;
rowRecord[row][number] = false;
regionRecord[(row / 3) * 3 + col / 3][number] = false;
}
public void setInvalid(char[][] board,int row,int col,boolean[][] colRecord,boolean[][] rowRecord,boolean[][] regionRecord){
int number = board[row][col] - '1';
colRecord[col][number] = true;
rowRecord[row][number] = true;
regionRecord[(row / 3) * 3 + col / 3][number] = true;
}
public boolean isValid(char c,int row,int col,boolean[][] colRecord,boolean[][] rowRecord,boolean[][] regionRecord){
int number = c - '1';
if (colRecord[col][number] || rowRecord[row][number] || regionRecord[(row / 3) * 3 + col / 3][number]) {
return false;
} else {
return true;
}
}
}