leetcode算法整理36有效的数独

房东的猫 提交于 2020-01-31 09:27:10

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

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

说明:

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

解法说明:在我看来,这道题不论是处理行,还是列,或者九宫格都不是问题,难点在于如何巧妙的判定1-9的不重复出现。
参考:https://leetcode-cn.com/problems/valid-sudoku/solution/java-wei-yun-suan-xiang-jie-miao-dong-zuo-biao-bia/
给出一种规范解法。
难点1:如何表示遍历一个九宫格呢?
在这里插入图片描述
上图不同颜色代表不同的九宫格。
我们知道某个位置的坐标是第几个宫格第几个位置,那如何知道它是哪一个坐标呢
A是5宫格3位置。
在这里插入图片描述
一行有三个九宫格,(5/3)*3表示5上面有3小行,一个小的九宫格每行有3小格,3/3代表在第5个九宫格中3位置上面还有1小行,1+3=4,说明A是第4行。
求列同理:(5%3)*3+3%3=6
给出规律:当前为第i九宫格第j个位置
则行列为board[3∗(i/3)+j/3] [3∗(i%3)+j%3]

难点二:如何表示9个数字均不相同?这里用位运算来解决。
本题可以使用一个10位二进制数判断数字是否被访问。第k位数为1代表已加入,为0代表未加入(从第0为开始,虽然此0位并无作用)。
初始时val=0,代表没有数加入。假设现在传入数字为3,我们将val右移三位,与1相与。
val右移三位还是 0 000 000 000 与0 000 000 001 相与,相与为0代表新加入,为1代表重复。这里很明显是加入数字。将1左移三位0 000 001 000与val异或为0 000 001 000 存为新的val。
再举一例:
val = 0 100 010 100 现在传入n=2,
val右移2为得到 val = 0 001 000 101与 0 000 000 001相与得到 0 000 000 001,所以表示重复。很明显看到原本val第二位是1,所以运算正确。
做法总结:
更新方式(记九位数为val,传入的数字为n):

判断是否加入:将十位数右移位n位,与1进行与运算
    结果为0:未加入,将传入的数字加入十位数
    结果为1:已加入,返回false
将传入的数字加入十位数:将1左移位n位,与val异或即可

代码:

public boolean isValidSudoku(char[][] board) {
    for(int i = 0; i < 9; i ++){
        // hori, veti, sqre分别表示行、列、小宫
        int hori = 0, veti = 0, sqre = 0;
        for(int j = 0; j < 9; j ++){
            // 由于传入为char,需要转换为int,减48 ascii '0'=48
            int h = board[i][j] - 48;//便利第i行
            int v = board[j][i] - 48;//遍历第i列
            int s = board[3 * (i / 3) + j / 3][3 * (i % 3) + j % 3] - 48; //遍历第i个九宫格
            // "."的ASCII码为46,故小于0代表着当前符号位".",不用讨论
            if(h > 0){
                hori = sodokuer(h, hori);
            }
            if(v > 0){
                veti = sodokuer(v, veti);
            }
            if(s > 0){
                sqre = sodokuer(s, sqre);
            }
            if(hori == -1 || veti == -1 || sqre == -1){
                return false;
            }
        }
    }
    return true;
}
//传入的值重复了,返回-1,正确传入返回更新的val
private int sodokuer(int n, int val){
    return ((val >> n) & 1) == 1 ? -1 : val ^ (1 << n);
}

另外参考https://leetcode-cn.com/u/angus-liu/
有另外一种巧妙解法,已做详细注释,不再做解释

class Solution {
    public boolean isValidSudoku(char[][] board) {
        // 记录某行,某位数字是否已经被摆放
        boolean[][] row = new boolean[9][9];
        // 记录某列,某位数字是否已经被摆放
        boolean[][] col = new boolean[9][9];
        // 记录某 3x3 宫格内,某位数字是否已经被摆放
        boolean[][] block = new boolean[9][9];
        //这解法真牛
        for (int i = 0; i < 9; i++) 
        {
            for (int j = 0; j < 9; j++) 
            {
                if (board[i][j] != '.') 
                {
                    //将char的1-9处理成int类型且数组不会溢出的0-8
                    //说明:其实九宫格中是哪九个数字并无差别,只要不相同即可,因为这里是存在数组中,0-8刚好符合数组的范围
                    int num = board[i][j] - '1';
                    //判断是哪个九宫格
                    int blockIndex = i / 3 * 3 + j / 3;
                    //当某行某列或某九宫格中出现重复数字是,if则判定成功,返回false
                    if (row[i][num] || col[j][num] || block[blockIndex][num]) 
                    {
                        return false;
                    } 
                    else 
                    {
                        //行,列,九宫格,中未曾出现过赋值为true
                        row[i][num] = true;
                        col[j][num] = true;
                        block[blockIndex][num] = true;
                    }
                }
            }
        }
        return true;
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!