我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 36. 有效的數獨 題目 判斷一個 9x9 的數獨是否有效。只 ...
我的LeetCode:https://leetcode-cn.com/u/ituring/
我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii
LeetCode 36. 有效的數獨
題目
判斷一個 9x9 的數獨是否有效。只需要__根據以下規則__,驗證已經填入的數字是否有效即可。
- 數字 1-9 在每一行只能出現一次。
- 數字 1-9 在每一列只能出現一次。
- 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。
上圖是一個部分填充的有效的數獨。
數獨部分空格內已填入了數字,空白格用 '.' 表示。
示例 1:
輸入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: true
示例 2:
輸入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: false
解釋: 除了第一行的第一個數字從 5 改為 8 以外,空格內其他數字均與 示例1 相同。
但由於位於左上角的 3x3 宮內有兩個 8 存在, 因此這個數獨是無效的。
說明:
- 一個有效的數獨(部分已被填充)不一定是可解的。
- 只需要根據以上規則,驗證已經填入的數字是否有效即可。
- 給定數獨序列只包含數字 1-9 和字元 '.' 。
- 給定數獨永遠是 9x9 形式的。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/valid-sudoku
著作權歸領扣網路所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
解題思路
這道題做出來是比較簡單的,暴力來每行每豎每小宮格分別校驗就可以了,這樣看至少需要三次全掃描;
如何做到只掃描一遍就能完成呢?分析後可能發現比較難解決是小宮格的索引問題,其次如果要保存原始數據,應該需要
橫豎小宮格共三個二維數組(或者set集合),但是我們只需要知道1-9這幾個數字有還是沒有以及只能有一個,對應到編碼就是0和1,因此可以進一步壓縮二維數組為一位數組,
每個數組的int的前9個bit位用來存儲1-9是否出現過且僅出現過一次,綜上,從需要三次掃描三個二維數組到一次掃描三個二維數組再到最後一次掃描三個一維數組,演算法的效率提升是非常明顯的;
思路1-三遍掃描,橫豎小宮格各自作校驗
步驟:
- 新建三個set對應橫豎小宮格;
- 三次掃描分別校驗橫豎小宮格;
思路2-只掃描一次
只掃描一次的關鍵是定位小宮格的索引:
關鍵點小宮格的索引公式:(i/3)*3 + j/3;
可以這麼理解:
- i/3表示第幾行的小宮格,取值為0、1、2對應實際的第一、二、三行;
- (i/3)3表示第i/3的小宮格下標的的起始數字,比如第一行是從0開始,第二行是從3開始,第三行是從6開始;
- j/3表示所在行的第j/3個宮格,取值為0、1、2對應實際的第一、二、三個小宮格;
- (i/3)3 + j/3 組合起來就是:前半段表示小宮格的所在行及起始下標,後半段表示當前行的第幾個小宮格,組合的所有可能值0、1、2、3、、4、5、6、7、8就對應九個小宮格;
思路3-只掃描一次,進一步壓縮輔助空間,使用bit位記錄數據
實際做法和代碼與思路2無太大區別,唯一的區別是數組換位一維的,校驗時使用位記錄和對比校驗;
演算法源碼示例
package leetcode;
import java.util.HashSet;
/**
* @author ZhouJie
* @date 2020年2月1日 下午4:30:05
* @Description: 36. 有效的數獨
*
*/
public class LeetCode_0036 {
}
class Solution_0036 {
/**
* @author ZhouJie
* @date 2020年2月1日 下午8:27:14
* @Description: TODO(方法簡述)
* @return boolean
* @UpdateUser-UpdateDate:[ZhouJie]-[2020年2月1日 下午8:27:14]
* @UpdateRemark:1-行列小宮格分別遍歷校驗,使用一個set
*
*/
public boolean isValidSudoku_1(char[][] board) {
if (board == null) {
return false;
}
HashSet<Character> set = new HashSet<Character>();
// 行列判斷
for (int x = 0; x < 9; x++) {
set.clear();
// 行判斷
for (int y = 0; y < 9; y++) {
if (!check(set, board[x][y])) {
return false;
}
}
set.clear();
// 列判斷
for (int y = 0; y < 9; y++) {
if (!check(set, board[y][x])) {
return false;
}
}
}
// 小宮格判斷,先定位單個小宮格位置,再遍歷小宮格的數字
for (int i = 0; i < 9; i += 3) {
for (int j = 0; j < 9; j += 3) {
set.clear();
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
if (!check(set, board[x + i][y + j])) {
return false;
}
}
}
}
}
return true;
}
private boolean check(HashSet<Character> set, char c) {
if (c == '.') {
return true;
} else if (set.contains(c)) {
return false;
} else {
set.add(c);
return true;
}
}
/**
* @author ZhouJie
* @date 2020年2月1日 下午8:27:38
* @Description: TODO(方法簡述)
* @return boolean
* @UpdateUser-UpdateDate:[ZhouJie]-[2020年2月1日 下午8:27:38]
* @UpdateRemark:2-行列小宮格各自使用自己的校驗空間同時校驗,一次遍歷
*
*/
public boolean isValidSudoku_2(char[][] board) {
// 行列小宮格的編號
int[][] rows = new int[9][9];
int[][] columns = new int[9][9];
int[][] boxes = new int[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
if (c != '.') {
int num = c - '1';
// 關註點,計算小宮格索引公式
int boxIndex = (i / 3) * 3 + j / 3;
// 行判斷
if (rows[i][num] == 1) {
return false;
} else {
rows[i][num] = 1;
}
// 列判斷
if (columns[j][num] == 1) {
return false;
} else {
columns[j][num] = 1;
}
// 小宮格判斷
if (boxes[boxIndex][num] == 1) {
return false;
} else {
boxes[boxIndex][num] = 1;
}
}
}
}
return true;
}
/**
* @author: ZhouJie
* @date: 2020年4月3日 下午9:30:03
* @param: @param board
* @param: @return
* @return: boolean
* @Description: 3-進一步壓縮空間,使bit位來計算數據
*
*/
public boolean isValidSudoku_3(char[][] board) {
// 行列小宮格的編號
int[] rows = new int[9];
int[] columns = new int[9];
int[] boxes = new int[9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
if (c != '.') {
int num = c - '1';
// 關註點,計算小宮格索引公式
int boxIndex = (i / 3) * 3 + j / 3;
// 行判斷
if (((rows[i] >> num) & 1) == 1) {
return false;
} else {
rows[i] |= 1 << num;
}
// 列判斷
if (((columns[j] >> num) & 1) == 1) {
return false;
} else {
columns[j] |= 1 << num;
}
// 小宮格判斷
if (((boxes[boxIndex] >> num) & 1) == 1) {
return false;
} else {
boxes[boxIndex] |= 1 << num;
}
}
}
}
return true;
}
}