01

题目描述

判断一个 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 形式的。

02

暴力

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

解法一

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

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

03

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];//初始化hashmapfor (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存下值加1rows[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;
}

解法四

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位设为10000
+   1
-------0001//把第二位设为10000
+ 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;
}

04

总结

前部分算法都是抛开标记暴力判断,整理完信息然后才判断有没有重复信息。再之后的解法是通过使用值做第二层容器的索引或者key,同一个值如果是同一列(块/行)就会存到同一个地方进而利用了第二层容器索引后可以在存的过程就判断是否有重,在之后这同一种思路在数据结构上有慢慢更好的选择,最终达到一个最优解。

本文分享自微信公众号 - IT那个小笔记(qq1839646816)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

LeetCode初级算法之数组:36 有效数独相关推荐

  1. LeetCode初级算法之数组:有效数独

    题目描述 判断一个 9x9 的数独是否有效.只需要根据以下规则,验证已经填入的数字是否有效即可. 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗 ...

  2. 初级算法_数组 --- 有效的数独

    1.题目 请你判断一个 9 x 9 的数独是否有效.只需要 根据以下规则 ,验证已经填入的数字是否有效即可: 数字 1-9 在每一行只能出现一次: 数字 1-9 在每一列只能出现一次: 数字 1-9 ...

  3. LeetCode初级算法(数组)解答

    这里记录了LeetCode初级算法中数组的一些题目: 加一 本来想先转成整数,加1后再转回去:耽美想到测试的例子考虑到了这个方法的笨重,所以int类型超了最大范围65536,导致程序出错. class ...

  4. LeetCode_初级算法_数组

    LeetCode|初级算法_数组 题目如下: 1.1 从排序数组中删除重复项 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间 ...

  5. leetcode初级算法4.两个数组的交集 II

    leetcode初级算法4.两个数组的交集 II 仅为个人刷题记录,不提供解题思路 题解与收获 我的解法:(总结在代码中) public int[] intersect(int[] nums1, in ...

  6. leetcode初级算法5.加一

    leetcode初级算法5.加一 仅为个人刷题记录,不提供解题思路 题解与收获 我的解法:(总结在代码中) public int[] plusOne(int[] digits) {//获取digits ...

  7. Leetcode 初级算法 - 设计问题

    Leetcode 初级算法 - 设计问题 原始地址:https://github.com/jerrylususu/leetcode-easy 二级标题格式:[章节内题号] [题库内题号] [题目标题] ...

  8. leetcode初级算法6.字符串转整数(atoi)

    leetcode初级算法6.字符串转整数(atoi) 仅为个人刷题记录,不提供解题思路 题解与收获 我的解法: public int myAtoi(String s) {//避免魔法值先设spaceS ...

  9. leetcode初级算法4.只出现一次的数字

    leetcode初级算法4.只出现一次的数字 仅为个人刷题记录,不提供解题思路 题解与收获 我的解法: public static int singleNumber(int[] nums) {if(n ...

最新文章

  1. SAP CRM webclient ui help link超链接的生成逻辑
  2. dlib人脸特征点对齐
  3. 编译时类型 和运行时类型的 区别(1)
  4. vue获取DOM元素并设置属性
  5. 41、OrthoMCL和mcl软件进行基因家族分析
  6. 什么叫做石英表_什么是石英表 石英表是什么意思
  7. matlab在循环中保存jpg格式_Matlab如何循环读取文件
  8. 【转】SVN ——开放源代码的版本控制系统
  9. Python:体脂计算
  10. python搭建自己的网站_Python+Django搭建自己的blog网站
  11. Bypass Windows AppLocker
  12. linux proc文件创建指南
  13. 【web】PHP网页调用Matlab代码的实现以及一些需要注意的bug~
  14. 涉黄网站爆四大生财链:站长月入近两万
  15. Powerbi 通过On-premises data gateway 连接本地多维数据库
  16. 芯片开发工具IAR Embedded Workbench for ARM 7.50.2
  17. 爬虫实例:每日一文和豆瓣电影
  18. 计算机2级mysql有用吗_计算机二级证书对程序员并没有什么卵用!
  19. matlab生成西门子plc源文件,西门子PLC编程软件STEP7 V5.5导出源文件
  20. Win11系统添加信任软件方法分享

热门文章

  1. 计算机在医学影像学的应用,计算机图像数字化与医学影像学之应用探析
  2. echarts xAxis字显示不全
  3. 成功解决 failing shard [AccessControlException[access denied (“java.io.FilePermission“
  4. 技术实践干货 | 初探大规模 GBDT 训练
  5. inception-v3模型神经网络图片识别系统搭建详细流程(1)
  6. java if经典程序_java经典程序题15道(另附自己做的答案)
  7. opencv remap matlab,C++ Opencv remap()重映射函数详解及使用示例
  8. pcb元器件焊接技巧
  9. 排位赛中 服务器崩溃修复后连不上,lol手游出现creating a lobby问题怎么办?服务器崩溃解决方法说明[多图]...
  10. 公司电子邮箱可以定制邮箱地址吗?