有效数独

题目地址:https://leetcode-cn.com/problems/valid-sudoku/

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 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

说明:

  • 一个有效的数独(部分已被填充)不一定是可解的。
  • 只需要根据以上规则,验证已经填入的数字是否有效即可。
  • 给定数独序列只包含数字 1-9 和字符 '.' 。
  • 给定数独永远是 9×9 形式的。

暴力

最直观的也就是按照题目流程的暴力解法,需要去判断每行每列每块有没有重复,那就去拿到每行每列每块的二维数组。判断这三组二维数组中的每个一维数组是有否重复。这里举个类似的4×4的例子(图画的少一点)

比如:

row:

[

['1','2','3','4'],

['5','.','6','.'],

['.','7','8','.'],

['7','.','.','3']

]

col:

[

['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;
}

其实这才是最直接的方式,也是效率最低的。

Hash表

根据上述情况第二层容器的索引没有意义,只用第一层容器的索引判断存到哪个第二层容器(块/列/行)最后对所有第二层容器查重所以才无关。那我这里我们可以用上第二层容器的索引或者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,同一个值如果是同一列(块/行)就会存到同一个地方进而利用了第二层容器索引后可以在存的过程就判断是否有重,在之后这同一种思路在数据结构上有慢慢更好的选择,最终达到一个最优解。

LeetCode初级算法之数组:36 有效数独的更多相关文章

  1. LeetCode初级算法之数组:48 旋转图像

    旋转图像 题目地址:https://leetcode-cn.com/problems/rotate-image/ 给定一个 n × n 的二维矩阵表示一个图像. 将图像顺时针旋转 90 度. 说明: ...

  2. LeetCode初级算法之数组:66 加一

    加一 题目地址:https://leetcode-cn.com/problems/plus-one/ 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一.最高位数字存放在数组的首位, 数 ...

  3. 算法练习LeetCode初级算法之数组

    删除数组中的重复项 官方解答: 旋转数组 存在重复元素 只出现一次的数     官方解答:  同一个字符进行两次异或运算就会回到原来的值 两个数组的交集 II import java.util.Arr ...

  4. LeetCode初级算法之数组:283 移动零

    移动零 题目地址:https://leetcode-cn.com/problems/move-zeroes/ 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺 ...

  5. LeetCode初级算法之数组:350 两个数组的交集 II

    两个数组的交集 II 题目地址:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/ 给定两个数组,编写一个函数来计算它们的交 ...

  6. LeetCode初级算法之数组:136 只出现一次的元素

    只出现一次的元素 题目地址:https://leetcode-cn.com/problems/single-number/ 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找 ...

  7. LeetCode初级算法之数组:189 旋转数组

    旋转数组 题目地址:https://leetcode-cn.com/problems/rotate-array/ 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数. 示例 1: 输 ...

  8. LeetCode初级算法之数组:122 买卖股票的最佳时机 II

    买卖股票的最佳时机 II 题目地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ 给定一个数组,它的第 i ...

  9. LeetCode初级算法之数组:1 两数之和

    两数之和 题目地址:https://leetcode-cn.com/problems/two-sum/ 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个整 ...

随机推荐

  1. pthread 多线程基础

    本文主要介绍如何通过 pthread 库进行多线程编程,并通过以下例子进行说明. 基于莱布尼兹级数计算 \(\pi\) . 多线程归并排序 参考文章: [1] https://computing.ll ...

  2. Idea eclipse 快捷键Debug调试

    运行下一行             F6 进入下一次计算      F5 运行到下一个断电  F7 恢复运行                F8

  3. 前端JS下载文件总结

    Data URLs Data URLs: 即前缀为data: 协议的URL,其允许内容创建者向文档中嵌入小文件. 例如:可以直接在HTML中的img元素直接使用Data URLs : data:[&l ...

  4. 一些 git 常用的命令

    1.本地命令 查看状态 -git status 添加文件 -git add . 提交文件 -git commit -m "(comment)" 查看历史key -git reflo ...

  5. 思维导图MindManager有新手引导功能吗

    无论是对于初次使用Mindmanager思维导图软件的新手来说,还是对于有一定软件使用基础的进阶者来说,Mindmanager思维导图软件的帮助功能都能给予用户很大的指导作用. Mindmanager ...

  6. D. Circle Game 题解(对称博弈)

    题目链接 题目大意 t组数据(t<=100) 给你一个半径d和步数k,你最开始在原点(0,0)每次可以让x坐标增加k,或者y坐标增加k 两人轮流走,求谁最后不能走了,谁就输了,都是最优博弈 输的 ...

  7. C语言讲义——二维数组

    二维数组,又称为矩形数组 可以不太准确地理解为"数组的数组" 也可以认为是一个表格 然而内存中并不是表格存储: 二维数组的初始化 第一维度可以省略 所有元素可以写在一个花括号中,计 ...

  8. C语言精华——内存管理,很多学校学习不到的知识~

    在编写程序时,通常并不知道需要处理的数据量,或者难以评估所需处理数据量的变动程度.在这种情况下,要达到有效的资源利用--使用内存管理,必须在运行时动态地分配所需内存,并在使用完毕后尽早释放不需要的内存 ...

  9. Pytest学习(十二)-生成HTML报告插件之pytest-html的使用

    环境前提 Python3.6+ 安装插件 pip3 install pytest-html -i http://pypi.douban.com/simple/ --trusted-host pypi. ...

  10. [BUGCASE]FixedDataTable表格数据渲染错误

    一.问题描述 广告配置中绑定第三方规格ID表格数据,有一部分展示错乱,具体如下: 表格组件使用 Facebook 的 (fixed-data-table) 组件 二.原因分析 1.检查props 先查 ...