leetcode 第36 题 , 题目如下,所示.
分析:
- 该题需要判断每一行,每一列,每一块是否有重复数字,最容想到的就是分别写代码去判断每一行,每一列是否有重复数字,用一个数组做存储容器,出现的数字作为数组的下标,出现为1,没有出现为0,这样在O(1)的时间内就可以知道数字是否重复,但是这样写代码比较冗余,需要遍历三遍,那能不能只遍历一遍呢?当然可以.
思路:
1 , 抛弃刚刚所想,更节省存储空间的话,我们可以用一个数字作为一个存储容器,利用数字二进制下的后九位,对应下标位置出现过变为1,没有出现保持0,以数字num为例,比如出现数字6,那么num的倒数第6位变为1,算数表达为:num | (1<<6),如果判断某一位是否为1,比如判断倒数第三位是否为1, (num>>3) & 1.
- 这里补充一下位运算基本知识.
- 按位或: |
有一个为1,则结果为1.否则该位结果为0 - 按位与: &
必须两个均为1,结果为1,否则该位结果为0. - 按位异或: ^
相同为0,不同为1.
2 , 如果想要只遍历一遍,那么我们就需要推导坐标之间的关系.
推导过程:
(1). 我们假设外层循环变量为ii,内层循环变量为jj,那么board[i][j]唯一表示第i 行,第j列的元素.
(2). 如何表示小宫内的格子?
-
由于一共有9个小宫,每个小宫有9格,我们可以先找到每个小宫的第0个元素坐标,然后加上每个元素相对应第0个元素的偏移量就可以.
-
如图所示,九个小宫用不同的颜色表示,每个小宫的左上角为小宫的第0个格子(深色标记)。我们设标号顺序为从左往右,从上往下。
-
那么:第i个小宫的第0个元素就是(3*(i/3),3*(i%3)),
-
每个小宫第j个元素相对于第0个元素的偏移量也就是(j/3,j%3)
-
那么第i小宫第j个元素也就是(3∗(i/3)+j/3,3∗(i%3)+j%3)
接下来就是代码了.如下.
class Solution {
public boolean isValidSudoku(char[][] board) {
if(board==null||board.length==0) return false;
for(int i=0;i<9;i++){
int line=0,list=0,square=0; //初始化三个容器
for(int j=0;j<9;j++){
int val_line=(int)board[i][j]-48; //获取每一行的值
int val_list=(int)board[j][i]-48; //每一列的值
int val_sq=(int)board[3*(i/3)+j/3][3*(i%3)+j%3]-48; //每一宫的值
if(val_line>0) //'.'的ASCLL码是46,转换为数字小于0.
line=sodokuer(val_line,line);
if(val_list>0)
list=sodokuer(val_list,list);
if(val_sq>0)
square=sodokuer(val_sq,square);
if(line==-1||list==-1||square==-1)
return false;
}
}
return true;
}
private int sodokuer(int val, int res){
//如果对应位上的数值为1,说明已经出现过,返回-1,否则对应位设为1
return ((res>>val) & 1) == 1 ? -1 : res ^ (1<<val);
}
}