Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >有效数独

有效数独

作者头像
木瓜煲鸡脚
发布于 2020-11-25 03:59:28
发布于 2020-11-25 03:59:28
68500
代码可运行
举报
文章被收录于专栏:Jasper小笔记Jasper小笔记
运行总次数:0
代码可运行

01

题目描述

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

示例1:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
输入:
[
["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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
输入:
[
["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的例子(图画的少一点)

解法一

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
}

上面解法实际上存在一个问题就是我们存完二维数组之后,再使用遍历查重的方式对每个单数组进行查重。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
col[j][i] = board[i][j];
box[(i/3)*3 + j/3][(i%3)*3 + j%3] = board[i][j];

也就是说在存这两个二维数组时只有只有第一个中括号的索引是有用的标记着是哪一列或者哪一块,但它是在一列(块/行)的哪个位置是无所谓的,因为最后单独用了单数组查重的方式(无论顺序怎么样只要是在一个容器,最后容器单独用方法判断是否有重)。在编码中第二个中括号写的索引只不过是保留了在面板上我们去数数的顺序,换成别的0-9不重复的也可以。

是否重复的关键也就是数值是否一样,是否是同一块(行/列)这些相同也就是无效数独,和在具体行(列/块)里面的哪个位置无关。也就可以这样写

解法二

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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把它的索引变得有意义,也就是等同于值。这样值就与位置相关,再存时就可以判断重复与否。而不用先存完之后在单独遍历每个第二层容器。

解法三

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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存下值加1
          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;
}

解法四

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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就是这个值已经存过这次是重复的

解法五(解法四优化)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//把第0位设为1
 0000
+   1
-------
 0001

//把第二位设为1
 0000
+ 100
-------
 0100

以前是数组,存一个9就找在这个数组索引8存下true,现在就是将1左移位运算8然后相加,同样是将一个值的第8位改为1。当前存的值是否是重复以前是判断这个地方是否已经是true,现在与num进行与运算看是不是0,比如存一个4,要存在第三位

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//第三位没有值
 001000101
&     1000 
----------
 000000000

//第三位有值
 001001000
&     1000
----------
 000001000

解法六(解法五优化)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,同一个值如果是同一列(块/行)就会存到同一个地方进而利用了第二层容器索引后可以在存的过程就判断是否有重,在之后这同一种思路在数据结构上有慢慢更好的选择,最终达到一个最优解。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT那个小笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
​LeetCode刷题实战36: 有效的数独
算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !
程序员小猿
2021/01/20
3730
​LeetCode刷题实战36: 有效的数独
LeetCode动画 | 37.解数独
今天分享一个LeetCode题,题号是37,题目标题是解数独,题目标签是散列表和回溯算法。
我脱下短袖
2020/02/25
5370
数据结构003:有效的数独
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
艰默
2022/12/12
4250
数据结构003:有效的数独
有效的数独(leetcode36)
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
Vincent-yuan
2021/04/09
3870
有效的数独(leetcode36)
如何用程序判断一个数独是否有效
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
MickyInvQ
2021/03/02
6900
如何用程序判断一个数独是否有效
数据结构003:有效的数独
根据题目的规则,数独需要满足三个规则,针对规则一和二可知,我们在遍历每个元素的时候,需要判断该元素所在行和列中是否出现过,即可判断该元素是否满足规则一和二,因此我们可以针对每一行、每一列出现元素的次数作为校验标准,例如声明两个二维数组row[9][9] 和col[9][9] 分别代表行和列上面0-9 出现的次数。例如row[1][2] 表示第1行中,出现2的次数,col[4][3] 表示第4列出现3的次数(都是从第0行/列开始算的)。对于数独数组第i 行j 列上的数值n=board[i][j] ,首先将row[i][n] 上对应的值加一,再将col[j][n] 也加一,然后判断row[i][n] 和row[i][n] 的值是否大于1,大于1则表明i 行或者j 列数字n 出现的次数大于1,即不唯一。不满足规则一或者二。
艰默
2022/12/11
7910
数据结构003:有效的数独
解数独
数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 空白格用 ‘.’ 表示。
你的益达
2020/10/15
6130
漫画:算法如何验证合法数独 | 全世界最难的数独?
今天是小浩算法 “365刷题计划” 第95天 。数独相信在座的各位都玩过,那我们如何使用程序去验证一个 9×9 的数独是有效的呢?一起看下!
程序员小浩
2020/05/26
8330
漫画:算法如何验证合法数独 | 全世界最难的数独?
【刷穿 LeetCode】36. 有效的数独(中等)
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
宫水三叶的刷题日记
2021/02/20
5430
python 有效的数独 多种解法
最简单的方法是对于每一行、每一列和每一个 3x3 的九宫格,分别判断其中是否有重复的数字。具体实现如下:
编程小白狼
2024/12/31
800
python 有效的数独 多种解法
LeetCode 图解 | 36.有效的数独
今天分享一个LeetCode题,题号是36,标题是:有效的数独,题目标签是散列表,散列表也称哈希表。此题解题思路用到了少量的空间换取时间的方法,降低时间上的消耗。
五分钟学算法
2020/02/20
6980
LeetCode 图解 | 36.有效的数独
探究解数独问题
在做这道题之前相比大家看到“困难”的flag就被吓到了;但是如何结合上一道也就是判断有效数独的灵活思路;其实仔细一想也不算困难。
羑悻的小杀马特.
2025/01/23
840
探究解数独问题
LeetCode题目36:有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
二环宇少
2020/08/13
4890
LeetCode题目36:有效的数独
36. 有效的数独--题解
请你判断一个 9x9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
付威
2021/04/25
3770
36. 有效的数独--题解
Leetcode No.36 有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
week
2022/01/07
3330
Leetcode No.36 有效的数独
LC36— 有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
Java架构师必看
2021/05/14
3290
算法刷题:LC初级算法(二)
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
看、未来
2021/09/18
3050
【leetbook刷题】有效的数独
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
薄荷冰
2024/01/22
1690
【leetbook刷题】有效的数独
☆打卡算法☆LeetCode 36、有效的数独 算法解析
链接:36. 有效的数独 - 力扣(LeetCode) (leetcode-cn.com)
恬静的小魔龙
2022/08/07
3780
☆打卡算法☆LeetCode 36、有效的数独  算法解析
算法:哈希表
哈希表:也叫做散列表。是根据关键字和值(Key-Value)直接进行访问的数据结构。也就是说,它通过关键字 key 和一个映射函数 Hash(key) 计算出对应的值 value,然后把键值对映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数(散列函数),用于存放记录的数组叫做 哈希表(散列表)。哈希表的关键思想是使用哈希函数,将键 key 和值 value 映射到对应表的某个区块中。可以将算法思想分为两个部分:
用户3578099
2022/04/18
2.6K0
算法:哈希表
相关推荐
​LeetCode刷题实战36: 有效的数独
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验