题目及测试用例
package pid036;
/*有效的数独
判断一个 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 形式的。
*/
public class main {
public static void main(String[] args) {
char[][] testTable1 ={
{'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'}
};
char[][] testTable2 ={
{'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'}
};
test(testTable1);
test(testTable2);
}
private static void test(char[][] ito) {
Solution solution = new Solution();
boolean rtn;
long begin = System.currentTimeMillis();
rtn = solution.isValidSudoku(ito);//执行程序
long end = System.currentTimeMillis();
System.out.print(rtn);
System.out.println();
System.out.println("耗时:" + (end - begin) + "ms");
System.out.println("-------------------");
}
}
解法1(成功,30ms,速度中等)
速度o(n),空间o(n)
具体在某一行,某一列,某个九宫格查找是否1-9重复的办法是
加入一个hashmap,以1-9为key,加入hashmap,到了新的地方就查找是否containskey
另外在99 对33进行循环的方法是k从0-9循环,3*3具体的左上角的index通过k模乘得到
package pid036;
import java.util.HashMap;
public class Solution {
public boolean isValidSudoku(char[][] board) {
int length=board.length;
int length2=board[0].length;
if(length!=9||length2!=9){
return false;
}
HashMap map=new HashMap();
//横着的row
for(int i=0;i
解法2(成功,6ms,很快)
使用一个boolean数组代替hashmap,节约空间和时间,每次将num-1 位变为true,如果已经为true,则重复,返回false
每次遍历后,清空数组,等待再次使用。
九宫格,先算出左上角顶点,然后for循环i 3次,j 3次。
如何算出顶点,第一个for循环indexi 3次(每次+3),indexj也一样。
或者k从0循环9次,indexi =(k%3)*3;indexj=k-begini/3;
或者从i,j倒推第几个九宫格, box_index = (row / 3) * 3 + columns / 3(直接设置9个数组,遍历81个元素,直接放入对应数组。)
public boolean isValidSudoku(char[][] board) {
boolean[] hasNum=new boolean[9];
//先对行
for(int j=0;j<9;j++){
for(int i=0;i<9;i++){
//如果插入结果为false,说明重复,返回false
if(!addIntoArray(i, j, hasNum, board)){
return false;
}
}
clearArray(hasNum);
}
//对列
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
//如果插入结果为false,说明重复,返回false
if(!addIntoArray(i, j, hasNum, board)){
return false;
}
}
clearArray(hasNum);
}
//对九宫格,indexi为九宫格左上角的row,indexj为九宫格左上角的col,两者每次加3
for(int indexi=0;indexi<9;indexi+=3){
for(int indexj=0;indexj<9;indexj+=3){
for(int i=indexi;i
解法3(别人的,应该成功,貌似很快,但实测30ms)
经典的位运算
n为1*2的a次方,化为二进制
比如
1 10
2 100
3 1000
4 10000
首先比如第二行 signs[0][1] 初始值为0
当0与不同的n进行或运算,这个值对应n的那个1的那位也变成了1
比如0|100=100 100|10=110
但是如果这个数与一个重复的数进行与运算,则
比如110&1000=0 110&100=100 则此时不为0,证明发生了重复
很高明的手法,本来用hashmap或hashset要用o(n)的空间,但用位运算只要一个int就可以了
首先也是循环每个元素节点,然后通过
int a = board[i][j] -'0';
转换成int类型,答案中写的是减1。始终不明白,后来在去掉不减的时候debug发现得到的a并不是原数,而是原数的Ascii码。岁而明白这是char转int的方法。
int n = 1 << a;
这个就是正常的按位左移运算了,主要用来判断是否有相同的元素存在。
if ((signs[0][i] & n) != 0
|| (signs[1][j] & n) != 0
|| (signs[2][cubeIndex] & n) != 0){
return false;
}
这段我认为是最难理解的地方,我们可以把它拆分来看,先看每行,其他先不关注。代码中使用了&,并且上边是按元素左位移的,所以只有当元素相等的情况下值才不回为0,&为相对应位都是1,则结果为1,否则为0。每一列、每一区块的判断逻辑都是如此。
signs[0][i] |= n;
signs[1][j] |= n;
signs[2][cubeIndex] |= n;
这段就是把循环的元素都合并到一起,这样才能保证在上边进行重复判断的时候所有的元素都会判断上。下面附上这种方案的完整解决方案:
public boolean isValidSudoku(char[][] board) {
int[][] signs = new int[3][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.'){
continue;
}
int a = board[i][j] -'0'; //char 转 int方法
int n = 1 << a;
int cubeIndex = i / 3 * 3 + j / 3;
if ((signs[0][i] & n) != 0
|| (signs[1][j] & n) != 0
|| (signs[2][cubeIndex] & n) != 0){
return false;
}
signs[0][i] |= n;
signs[1][j] |= n;
signs[2][cubeIndex] |= n;
}
}
return true;
}