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

题目描述

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

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 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
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。

初步思路:

这个题的思路还是挺明确的,不管是采用暴力的方式还是什么方式,无非就是三个判断,我遍历数组,只要保证每一行没有重复的,每一列没有重复的,每一个九宫格没有重复的,那么这个就是有效数独。
所以,我得第一次解法是

这个题初步想法写三个判断法则
1. 遍历行
每一行, 建一个map用来统计数的个数,如果不是1, 返回false
2. 遍历列
每一列,和行的思路一样
3. 遍历每个九宫格
九宫格的遍历有点复杂,初步想法就是把九宫格中心的坐标先以键值对的形式存入数组,然后遍历数组,对每一个九宫格,建一个map统计当前九宫格里面出现的数字数量,如果大于1,返回false。 即用九宫格中心的那个点代表一个九宫格。

所以就根据上述的思想完成了第一个版本的代码如下:

class Solution
{
public:
    bool isValidSudoku(vector<vector<char> >& board)
    {

        int rowsize = board.size();
        int colsize = board[0].size();

        // 遍历每一行,不能有重复的
        for (int i=0; i<rowsize; i++)
        {
            map<char, int> m;
            map<char, int> ::iterator it;
            for (int j=0; j<colsize; j++)
            {
                if (board[i][j] != '.')
                {
                    m[board[i][j]]++;
                }
            }
            for (it=m.begin(); it!=m.end(); it++)
            {
                if (it->second >1)
                    return false;
            }

        }

        // 遍历每一列,不能有重复的
        for (int i=0; i<colsize; i++)
        {
            map<char, int>m;
            map<char, int> ::iterator it;
            for (int j=0; j<rowsize; j++)
            {
                if (board[j][i] != '.')
                {
                    m[board[j][i]] ++;
                }
            }

            for (it=m.begin(); it!=m.end(); it++)
            {
                if (it->second >1)
                    return false;
            }
        }

        // 遍历每个九宫格
        // 先建立一个二维数组,把每个九宫格的中心坐标找好
        int loc[] = {1, 1,
                     1, 4,
                     1, 7,
                     4, 1,
                     4, 4,
                     4, 7,
                     7, 1,
                     7, 4,
                     7, 7
                    };
        for (int i=0; i<17; i+=2)
        {
            // 获取九空格的中心坐标
            int locrownum = loc[i];
            int loccolnum = loc[i+1];

            // 每一个九空格,判断是否有重复的数字
            map<char, int> m;
            if( board[locrownum-1][loccolnum-1] != '.')
            {
                m[board[locrownum-1][loccolnum-1]]++;
            }
            if(board[locrownum-1][loccolnum] != '.')
            {
                m[board[locrownum-1][loccolnum]]++;
            }
            if(board[locrownum-1][loccolnum+1] != '.')
            {
                m[board[locrownum-1][loccolnum+1]]++;
            }
            if(board[locrownum][loccolnum-1] != '.')
            {
                m[board[locrownum][loccolnum-1]]++;
            }
            if(board[locrownum][loccolnum] != '.')
            {
                m[board[locrownum][loccolnum]]++;
            }
            if(board[locrownum][loccolnum+1] != '.')
            {
                m[board[locrownum][loccolnum+1]]++;
            }
            if(board[locrownum+1][loccolnum-1] != '.')
            {
                m[board[locrownum+1][loccolnum-1]]++;
            }
            if(board[locrownum+1][loccolnum] != '.')
            {
                m[board[locrownum+1][loccolnum]]++;
            }
            if(board[locrownum+1][loccolnum+1] != '.')
            {
                m[board[locrownum+1][loccolnum+1]]++;
            }
            // 弄一个迭代器,进行有效区分
            map<char, int> ::iterator it;
            for (it=m.begin(); it!=m.end(); it++)
            {
                if (it->second >1)
                    return false;
            }
        }

        return true;
    }
};

这个代码A掉这个题目没有任何问题,但是太繁琐了,尤其是九宫格那竟然能写九遍判断也是没谁了,完全是暴力思想了。 所以看了看人家的题解,对这个题做了一个优化,没想到两次循环遍历一遍就可以看出是不是有效数独。

改进思路:

思想依然是同样的思想,但是在效率上有了提高,就是我用一次遍历所有的元素就可以判断出是不是有效数独。 这里的关键是弄清楚每一个子数独和整个数独的坐标关系:借助官方的图来理解:

看上面这个图,99的一个大数独是由9个33的小数独组成的,编号0-8,那么遍历大数独的时候,i, j与每一个小数独的编号的关系是

box_index = (i / 3) * 3 + j / 3

所以基于这个关系式,就可以写思路了:

首先,我要见三个9行10列的二维数组,每一个统计行,列,子宫格里面元素的出现的次数
然后,遍历这个大数独, 对于出现的每一个元素,如果不是".",那么相应的统计个数的数组相应的位置就进行加1, 如果发现重复了,返回false。

思路看着还是有点晕乎,看代码吧。

class Solution
{
public:
    bool isValidSudoku(vector<vector<char> >& board)
    {
		// 建立三个9行10列(0-9)的二维数组统计个数
        vector<vector<int> > rows(9, vector<int>(10, 0));  // 9行的每一行是否有重复的   
        vector<vector<int> > cols(9, vector<int>(10, 0));  // 9列的每一列里面是否有重复的
        vector<vector<int> > cell(9, vector<int>(10, 0));  // 9个子宫格的每一个子宫格里面有没有重复的(注意,这里得经过那个i,j的映射)
        for (int i = 0; i < 9; ++i)
        {
            for (int j = 0; j < 9; ++j)
            {
                if(board[i][j] != '.')
                {
                    int e = board[i][j] - '0';
                    // 判断行中是否有重复的
                    if (rows[i][e] == 0)
                        rows[i][e]++;
                    else
                        return false;

                    // 判断列中是否有重复的
                    if (cols[j][e] == 0)
                        cols[j][e]++;
                    else
                        return false;

                    // 判断小的九宫格里面是否有重复的
                    int ci = map(i, j);
                    if (cell[ci][e] == 0)
                        cell[ci][e]++;
                    else
                        return false;
                }
            }
        }
        return true;
    }

    int map(int i, int j)
    {
        int a = i / 3;
        int b = j / 3;
        return a*3 + b;
    }
};

小总一下:

这个题总体上思路比较清晰,开门见山,但是操作起来,如果不知道那个子宫格和整体的数独之间的那个对应关系的话,很难想到后面的改进思路这一个,通过一次遍历就判断出来,所以还是挺佩服我第一种解法的,这次以解决问题为根本目标了,好与坏,先解决出来再说。其次就是学到了vector创建二维数组并且使用的技巧,并且通过资料整理了一下:
https://blog.csdn.net/wuzhongqiang/article/details/103213734
PS: 这个题看过题解之后,发现还有一种位运算的解法,看了很长时间没怎么看明白,就先不整理了,可以参考一下:
在这里插入图片描述
https://leetcode-cn.com/problems/valid-sudoku/solution/java-wei-yun-suan-xiang-jie-miao-dong-zuo-biao-bia/

发布了66 篇原创文章 · 获赞 67 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wuzhongqiang/article/details/103214247