题目地址:https://leetcode-cn.com/problems/valid-sudoku/java
判断一个 9x9 的数独是否有效。只须要根据如下规则,验证已经填入的数字是否有效便可。算法
示例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编码
说明:code
最直观的也就是按照题目流程的暴力解法,须要去判断每行每列每块有没有重复,那就去拿到每行每列每块的二维数组。判断这三组二维数组中的每一个一维数组是有否重复。这里举个相似的4×4的例子(图画的少一点)索引
好比:leetcode
row:
[
['1','2','3','4'],
['5','.','6','.'],
['.','7','8','.'],
['7','.','.','3']
]getcol:
[
['1','5','.','7'],
['2','.','7','.'],
['3','6','8','.'],
['4','.','.','3']
]box:
[
['1','2','5','.'],
['3','4','6','.'],
['.','7','7','.'],
['8','.','.','3']
]
解法一
public boolean isValidSudoku(char[][] board) { //传入的board至关于就是row因此只须要初始化两个数组 char[][] col = new char[9][9]; char[][] box = new char[9][9]; //1.拿到三个二维数组分别表示多行数组,多列数组以及多块数组 for(int i = 0; i < 9; i++){ for(int j = 0; j < 9; j++){ col[j][i] = board[i][j]; box[(i/3)*3 + j/3][(i%3)*3 + j%3] = board[i][j]; } } //2.遍历每一个二维数组中的数组判断有无重复 for(int i = 0; i < 9; i++){ if(checkRepeat(board[i])){ return false; } if(checkRepeat(col[i])){ return false; } if(checkRepeat(box[i])){ return false; } } return true; } /** 检测单数组是否重复 */ public boolean checkRepeat(char[] arr){ for(int i = 0; i < 9; i++){ for(int j = i+1; j < 9; j++){ if(arr[i] != '.' && arr[j] != '.') if(arr[i] == arr[j]){ return true; } } } return false; }
上面解法实际上存在一个问题就是咱们存完二维数组以后,再使用遍历查重的方式对每一个单数组进行查重。
col[j][i] = board[i][j]; box[(i/3)*3 + j/3][(i%3)*3 + j%3] = board[i][j];
也就是说在存这两个二维数组时只有只有第一个中括号的索引是有用的标记着是哪一列或者哪一块,但它是在一列(块/行)的哪一个位置是无所谓的,由于最后单独用了单数组查重的方式(不管顺序怎么样只要是在一个容器,最后容器单独用方法判断是否有重)。在编码中第二个中括号写的索引只不过是保留了在面板上咱们去数数的顺序,换成别的0-9不重复的也能够。
是否重复的关键也就是数值是否同样,是不是同一块(行/列)这些相同也就是无效数独,和在具体行(列/块)里面的哪一个位置无关。也就能够这样写
解法二
public boolean isValidSudoku(char[][] board) { HashSet<String> set = new HashSet(); //遍历存值 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { char num = board[i][j]; if (num != '.') { int n = num - '0'; int box_index = (i / 3 ) * 3 + j / 3; String row = n + "是第"+i+"行"; String col = n + "是第"+j+"列"; String box = n + "是第"+box_index+"块"; if(!set.add(row) || !set.add(col) || !set.add(box)) return false; } } } return true; }
其实这才是最直接的方式,也是效率最低的。
根据上述状况第二层容器的索引没有意义,只用第一层容器的索引判断存到哪一个第二层容器(块/列/行)最后对全部第二层容器查重因此才无关。那我这里咱们能够用上第二层容器的索引或者key把它的索引变得有意义,也就是等同于值。这样值就与位置相关,再存时就能够判断重复与否。而不用先存完以后在单独遍历每一个第二层容器。
解法三
public boolean isValidSudoku(char[][] board) { //初始化数组 HashMap<Integer, Integer> [] rows = new HashMap[9]; HashMap<Integer, Integer> [] columns = new HashMap[9]; HashMap<Integer, Integer> [] boxes = new HashMap[9]; //初始化hashmap for (int i = 0; i < 9; i++) { rows[i] = new HashMap<Integer, Integer>(); columns[i] = new HashMap<Integer, Integer>(); boxes[i] = new HashMap<Integer, Integer>(); } //遍历存值 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { char num = board[i][j]; if (num != '.') { int n = (int)num; int box_index = (i / 3 ) * 3 + j / 3; //以数值为key存下 rows[i].put(n, rows[i].getOrDefault(n, 0) + 1); columns[j].put(n, columns[j].getOrDefault(n, 0) + 1); boxes[box_index].put(n, boxes[box_index].getOrDefault(n, 0) + 1); //判断当前(行、列、块)此值有木有重复 if (rows[i].get(n) > 1 || columns[j].get(n) > 1 || boxes[box_index].get(n) > 1) return false; } } } return true; }
按照上述解法存值标记key,经过key来判断有没有存过。一样也能够用数组实现用索引标记值和用key标记值是同样的而且效率上也会更快。因存的数值就是1-9,那就把9存到第九个也就是索引8。这样存进一个容器同一个数字就必定在同一个地方,经过值能够找到索引,经过索引又能够找到目前存的值。那么和map用key是同样的。都是作到用数值标记位置,那么同一个值即同一个地方在这个地方判断是否有重、
解法四
public boolean isValidSudoku(char[][] board) { //初始化数组 char[][] rows = new char[9][9]; char[][] columns = new char[9][9]; char[][] boxes = new char[9][9]; //遍历存值 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { char num = board[i][j]; if (num != '.') { int n = num - '0'; int box_index = (i / 3 ) * 3 + j / 3; //以数值为索引存下 if(rows[i][n - 1] == num) return false; if(columns[j][n - 1] == num) return false; if(boxes[box_index][n - 1] == num) return false; rows[i][n - 1] = num; columns[j][n - 1] = num; boxes[box_index][n - 1] = num; } } } return true; }
两种解法同一个思路只是选取的数据结构不一样,一样是使用值来标记位置,经过值就能查找位置。所以若是有一样的值就在同一个位置能够去判断。map是以值为key来实现,数组在此情景下由于数独盘面是9×9,里面的数字只能是1到9,因此数字若是是1就存在0位,是4就存在索引3的位置。经过值减一固定存的位置。
但上面数组解法还是存在疏漏浪费了空间,经过遍历的一个数我拿到这个数找对应位置的数值是否和这个数相等。仔细想想后面这一部分就是废话,我都存同一个索引或者同一个key了就是值相同,何须还去取再比较。只有两种状况这个地方没有存过那就是那就是null和当前值不一样,而后存事后再有一个往这个索引或者key存那就是重复了不用比。由于索引和key就是值。所以改为boolean,没存过都是false,存了就是true,再存同一个地方时发现是true就是这个值已经存过此次是重复的
解法五(解法四优化)
public boolean isValidSudoku(char[][] board) { //初始化数组 boolean[][] rows = new boolean[9][9]; boolean[][] columns = new boolean[9][9]; boolean[][] boxes = new boolean[9][9]; //遍历存值 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { char num = board[i][j]; if (num != '.') { int n = num - '0'; int box_index = (i / 3 ) * 3 + j / 3; //以数值为索引取看是否设过值 if(rows[i][n - 1]) return false; if(columns[j][n - 1]) return false; if(boxes[box_index][n - 1]) return false; rows[i][n - 1] = true; columns[j][n - 1] = true; boxes[box_index][n - 1] = true; } } } return true; }
有了解法四以后其实是更加能够进行优化的,由于结果标记只存在是与否两种状态,那咱们何不用0/1位。能够进一步减小空间那咱们须要一个9位的数字用byte类型只有1字节8位少了,因此只能用尽可能小的short类型2字节也就是16位咱们只用低9位。
如今用9位的一个数字(忽略高位)代替以前的9个元素的数组,也就没有子数组了
先以一个4×4的面板数字1-4作例子
扫描到第一个元素1那么它首先是第0块,在第0块里存第0位(数值-1)。那么上一个解法是子数组把里面的第0个设为true。如今不是子数组而是一个4位数把个位设成1。若是数字是3也就是把第2位设为1(true)
//把第0位设为1 0000 + 1 ------- 0001 //把第二位设为1 0000 + 100 ------- 0100
之前是数组,存一个9就找在这个数组索引8存下true,如今就是将1左移位运算8而后相加,一样是将一个值的第8位改成1。当前存的值是不是重复之前是判断这个地方是否已是true,如今与num进行与运算看是否是0,好比存一个4,要存在第三位
//第三位没有值 001000101 & 1000 ---------- 000000000 //第三位有值 001001000 & 1000 ---------- 000001000
解法六(解法五优化)
public boolean isValidSudoku(char[][] board) { //初始化数组 short[] rows = new short[9]; short[] columns = new short[9]; short[] boxes = new short[9]; //遍历存值 for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { char num = board[i][j]; if (num != '.') { num =(char)(1 << (num - '0')); int box_index = (i / 3 ) * 3 + j / 3; if((rows[i] & num) != 0) return false; if((columns[j] & num) != 0) return false; if((boxes[box_index] & num) != 0) return false; rows[i] += num; columns[j] += num; boxes[box_index] += num; } } } return true; }
前部分算法都是抛开标记暴力判断,整理完信息而后才判断有没有重复信息。再以后经过值作第二层容器的索引或者key,同一个值若是是同一列(块/行)就会存到同一个地方进而利用了第二层容器索引后能够在存的过程就判断是否有重,在以后这同一种思路在数据结构上有慢慢更好的选择,最终达到一个最优解。