|
| 1 | +--- |
| 2 | +title: leetcode36 有效的数独【中等难度】|极客学长 |
| 3 | +tags: |
| 4 | + - leetcode |
| 5 | + - 学习笔记 |
| 6 | + - 算法 |
| 7 | +--- |
| 8 | + |
| 9 | +### [36. 有效的数独](https://leetcode-cn.com/problems/valid-sudoku/) |
| 10 | + |
| 11 | +### 英文题目: Valid sudoku |
| 12 | + |
| 13 | + |
| 14 | +<table> <tr> <td bgcolor=white width=auto> ● 难度: </td> <td bgcolor=#F0AD4E width=auto><font color=white>中等</font></td> </tr></table> |
| 15 | + |
| 16 | + |
| 17 | +请你判断一个 `9x9` 的数独是否有效。只需要 **根据以下规则** ,验证已经填入的数字是否有效即可。 |
| 18 | + |
| 19 | +1. 数字 `1-9` 在每一行只能出现一次。 |
| 20 | +2. 数字 `1-9` 在每一列只能出现一次。 |
| 21 | +3. 数字 `1-9` 在每一个以粗实线分隔的 `3x3` 宫内只能出现一次。(请参考示例图) |
| 22 | + |
| 23 | +数独部分空格内已填入了数字,空白格用 `'.'` 表示。 |
| 24 | + |
| 25 | +**注意:** |
| 26 | + |
| 27 | +- 一个有效的数独(部分已被填充)不一定是可解的。 |
| 28 | +- 只需要根据以上规则,验证已经填入的数字是否有效即可。 |
| 29 | + |
| 30 | + |
| 31 | + |
| 32 | +**示例 1:** |
| 33 | + |
| 34 | + |
| 35 | + |
| 36 | +``` |
| 37 | +输入:board = |
| 38 | +[["5","3",".",".","7",".",".",".","."] |
| 39 | +,["6",".",".","1","9","5",".",".","."] |
| 40 | +,[".","9","8",".",".",".",".","6","."] |
| 41 | +,["8",".",".",".","6",".",".",".","3"] |
| 42 | +,["4",".",".","8",".","3",".",".","1"] |
| 43 | +,["7",".",".",".","2",".",".",".","6"] |
| 44 | +,[".","6",".",".",".",".","2","8","."] |
| 45 | +,[".",".",".","4","1","9",".",".","5"] |
| 46 | +,[".",".",".",".","8",".",".","7","9"]] |
| 47 | +输出:true |
| 48 | +``` |
| 49 | + |
| 50 | +**示例 2:** |
| 51 | + |
| 52 | +``` |
| 53 | +输入:board = |
| 54 | +[["8","3",".",".","7",".",".",".","."] |
| 55 | +,["6",".",".","1","9","5",".",".","."] |
| 56 | +,[".","9","8",".",".",".",".","6","."] |
| 57 | +,["8",".",".",".","6",".",".",".","3"] |
| 58 | +,["4",".",".","8",".","3",".",".","1"] |
| 59 | +,["7",".",".",".","2",".",".",".","6"] |
| 60 | +,[".","6",".",".",".",".","2","8","."] |
| 61 | +,[".",".",".","4","1","9",".",".","5"] |
| 62 | +,[".",".",".",".","8",".",".","7","9"]] |
| 63 | +输出:false |
| 64 | +解释:除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。 |
| 65 | +``` |
| 66 | + |
| 67 | + |
| 68 | + |
| 69 | +**提示:** |
| 70 | + |
| 71 | +- `board.length == 9` |
| 72 | +- `board[i].length == 9` |
| 73 | +- `board[i][j]` 是一位数字或者 `'.'` |
| 74 | + |
| 75 | +<br/> |
| 76 | + |
| 77 | +## 分析 |
| 78 | + |
| 79 | +### 方法1、蛮力直接法 |
| 80 | + |
| 81 | +使用set, |
| 82 | +对于行遍历: 每一行中, isValid: unique的数字数量+'.'的数量 = 9, |
| 83 | +对于列遍历:每一列中, isValid: unique的数字数量+'.'的数量 = 9, |
| 84 | +对于box遍历:每个3行3列九宫格中,isValid: unique的数字数量+'.'的数量 = 9。 |
| 85 | + |
| 86 | +<br/> |
| 87 | + |
| 88 | +已AC代码: |
| 89 | +```cpp |
| 90 | +class Solution { |
| 91 | +public: |
| 92 | + bool isValidSudoku(vector<vector<char>> &board) |
| 93 | + { |
| 94 | + bool isValid = true; |
| 95 | + |
| 96 | + // 遍历行 |
| 97 | + for (int i = 0; i < 9; i++) |
| 98 | + { |
| 99 | + set<char> st; |
| 100 | + vector<char> rowVec = board[i]; |
| 101 | + int dotCount = 0; |
| 102 | + for (int k = 0; k < 9; k++) |
| 103 | + { |
| 104 | + if (rowVec[k] == '.') |
| 105 | + { |
| 106 | + dotCount++; |
| 107 | + } |
| 108 | + else |
| 109 | + st.insert(rowVec[k]); |
| 110 | + } |
| 111 | + int uniqueCharCount = st.size(); |
| 112 | + if (uniqueCharCount + dotCount != 9) |
| 113 | + { |
| 114 | + isValid = false; |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + // 遍历列 |
| 119 | + for (int i = 0; i < 9; i++) |
| 120 | + { |
| 121 | + set<char> st; |
| 122 | + int dotCount = 0; |
| 123 | + for (int k = 0; k < 9; k++) |
| 124 | + { |
| 125 | + if (board[k][i] == '.') |
| 126 | + { |
| 127 | + dotCount++; |
| 128 | + } |
| 129 | + else |
| 130 | + st.insert(board[k][i]); |
| 131 | + } |
| 132 | + int uniqueCharCount = st.size(); |
| 133 | + if (uniqueCharCount + dotCount != 9) |
| 134 | + { |
| 135 | + isValid = false; |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + // 遍历小grid: 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 |
| 140 | + for (int si = 0; si <= 6; si += 3) |
| 141 | + for (int sj = 0; sj <= 6; sj += 3) |
| 142 | + { |
| 143 | + set<char> st; |
| 144 | + int dotCount = 0; |
| 145 | + for (int i = si; i < si + 3; i++) |
| 146 | + { |
| 147 | + for (int j = sj; j < sj + 3; j++) |
| 148 | + { |
| 149 | + if (board[i][j] == '.') |
| 150 | + dotCount += 1; |
| 151 | + else |
| 152 | + st.insert(board[i][j]); |
| 153 | + } |
| 154 | + } |
| 155 | + if (st.size() + dotCount != 9) |
| 156 | + isValid = false; |
| 157 | + } |
| 158 | + return isValid; |
| 159 | + } |
| 160 | +}; |
| 161 | +``` |
| 162 | + |
| 163 | +<br/> |
| 164 | + |
| 165 | +跟国外的小伙伴想到一块去了。 |
| 166 | +<https://leetcode.com/problems/valid-sudoku/discuss/869625/easy-C%2B%2B-with-set> |
| 167 | + |
| 168 | +<br/> |
| 169 | + |
| 170 | + |
| 171 | +### 方法2:set插入方法 - 改进 |
| 172 | + |
| 173 | +坐标中任意一点(i,j),可以map到对应的的第几行第几列的方块(box)中,box的坐标为(i/3, j/3)。 |
| 174 | + |
| 175 | +于是把一个小的九宫格中的数全压缩到一个box中,比如: |
| 176 | + |
| 177 | + |
| 178 | + |
| 179 | +<br/> |
| 180 | + |
| 181 | +以最中间那个九宫格为例,使用int型的/3可以得到: |
| 182 | + |
| 183 | + |
| 184 | + |
| 185 | + |
| 186 | +对于任意一个值不为'.'的字符,进行如下操作: |
| 187 | + |
| 188 | +1.把所在row的信息插入到大九宫格中; |
| 189 | + |
| 190 | +2.把所在column的信息插入到大九宫格中; |
| 191 | + |
| 192 | +3.把所在的小方块(box)的信息插入到大九宫格中。 |
| 193 | + |
| 194 | +插入如果失败说明出现了重复。 |
| 195 | + |
| 196 | + |
| 197 | +#### 已AC的C++代码: |
| 198 | + |
| 199 | +```cpp |
| 200 | +class Solution { |
| 201 | +public: |
| 202 | + bool isValidSudoku(vector<vector<char>>& board) { |
| 203 | + set<string> st; |
| 204 | + for (int i = 0; i < 9; i++) { |
| 205 | + for (int j = 0; j < 9; j++) { |
| 206 | + char ch = board[i][j]; |
| 207 | + // 使用i / 3 + "," + j / 3 得到对应第几行第几列的方块(box) |
| 208 | + if (ch != '.'){ |
| 209 | + string val; |
| 210 | + val.push_back(ch); |
| 211 | + /* 对于任意一个值不为'.'的字符 |
| 212 | + 1.把所在row的信息插入到大九宫格中; |
| 213 | + 2.把所在column的信息插入到大九宫格中; |
| 214 | + 3.把所在的小方块(box)的信息插入到大九宫格中。 |
| 215 | + 插入如果失败说明出现了重复。 */ |
| 216 | + if (!st.insert(val + " in row " + to_string(i)).second || |
| 217 | + !st.insert(val + " in column " + to_string(j)).second || |
| 218 | + !st.insert(val + " in box " + to_string(i / 3) + "," + to_string(j / 3)).second) |
| 219 | + return false; /* set插入失败时,表示出现了重复 */ |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + return true; |
| 224 | + } |
| 225 | +}; |
| 226 | +``` |
| 227 | +
|
| 228 | +Java的HashSet有同样的写法,Java中插入失败,会出现 `set.Add() == false`。 |
| 229 | +
|
| 230 | +
|
| 231 | +### 方法3:使用位操作 |
| 232 | +
|
| 233 | +此题,使用位操作,是几种解法中速度最快的算法了。 |
| 234 | +
|
| 235 | +具体做法是: |
| 236 | +
|
| 237 | + |
| 238 | +
|
| 239 | +将大数独棋盘分成9个小棋盘,编号0~8。 |
| 240 | +
|
| 241 | +窗口中的每个小方格若有数字,必为 1 ~ 9 (记作k),该方法适用于 遍历行/遍历列/遍历box。 |
| 242 | +
|
| 243 | +然后把 二进制数 1 左移 k 位,得到偏移量shift,后续使用按位或`|`来判断是否存在。 |
| 244 | +
|
| 245 | +
|
| 246 | +#### 已AC的C++代码: |
| 247 | +
|
| 248 | +```cpp |
| 249 | +class Solution { |
| 250 | +public: |
| 251 | + bool isValidSudoku(vector<vector<char>>& board) { |
| 252 | + vector<int> row(9); // row[j]表示第j 行的9个数字各自的存在情况,同理于col, boxes |
| 253 | + vector<int> col(9); |
| 254 | + vector<int> boxes(9); |
| 255 | +
|
| 256 | + int shiftInt = 0; |
| 257 | + for (int i = 0; i < 9; i++) |
| 258 | + { |
| 259 | + for (int j = 0; j < 9; j++) |
| 260 | + { |
| 261 | + if (board[i][j] == '.') |
| 262 | + continue; |
| 263 | +
|
| 264 | + shiftInt = 1 << (board[i][j] - '0'); // 转为二进制,移位结束后目标位为1,其他位均为0 |
| 265 | + /* 每个格子若有数字,必为 1 ~ 9,该方法适用于 遍历行/遍历列/遍历box */ |
| 266 | + int boxPos = (i / 3) * 3 + j / 3; //将大数独棋盘分成9个小棋盘,编号0~8 |
| 267 | +
|
| 268 | + // 如果当前数字shiftInt在row[j] 或col[i] 或 boxes中已经存在,&运算后不为0, |
| 269 | + // 只有当前数字没出现过,&运算后为0 |
| 270 | + if ((col[i] & shiftInt) != 0 || (row[j] & shiftInt) != 0 || (boxes[boxPos] & shiftInt) != 0) |
| 271 | + return false; |
| 272 | +
|
| 273 | + //第 n 位代表 n 这个数字是否存在(1→存在, 0→不存在),同理于col[i] boxes[boxPos] |
| 274 | + row[j] |= shiftInt; |
| 275 | + col[i] |= shiftInt; |
| 276 | + boxes[boxPos] |= shiftInt; |
| 277 | + } |
| 278 | + } |
| 279 | + return true; |
| 280 | + } |
| 281 | +}; |
| 282 | +``` |
| 283 | + |
| 284 | +后两种方法,参考: |
| 285 | + |
| 286 | +<https://leetcode-cn.com/problems/valid-sudoku/solution/wei-yun-suan-qiu-jie-you-xiao-shu-du-c-b-sac7/> |
| 287 | + |
| 288 | +<https://www.youtube.com/watch?v=ceOLAY4XUOw&ab_channel=JacobHuang> |
0 commit comments