这是我参与更文挑战的第 13 天,活动详情查看 更文挑战
这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
120. 有效的数独 (valid-sudoku)
标签
- Array
- 中等
题目
这里不贴题了,leetcode打开就行,题目大意:
请你判断一个 9x9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可
。
- 数字 1-9 在每一行只能出现一次。
- 数字 1-9 在每一列只能出现一次。
- 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
注意:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
示例 1:
输入:board =
[["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:
输入:board =
[["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 存在, 因此这个数独是无效的。
基本思路
判断是否合法其实比较简单,就是用横,纵,子盒子(3x3)
设置3个数组,然后依次判断每个格子,这个格子里的数如果跟已知当前行列或子盒子的数重复
,就说明不合法,否则就是合法,并在这三个数组中推入,表示该位置填上当前数。
行列,用 i, j
直接判断行列即可,子盒子的 index 判断稍微复杂些,看这个图
box_index = (i / 3) * 3 + j / 3
,其中 /
是整数除法
知道 (i, j)
我们就能知道是哪个子盒子,也就是根据坐标,获取所在的子盒子的索引
写法实现
const isValidSudoku = (board) => {
// 横,纵,小盒子的初始化,表示已经填上的数字,开始都是空的
let rowsUsed = new Array(9).fill(0).map(it => [])
let colsUsed = new Array(9).fill(0).map(it => [])
let boxesUsed = new Array(9).fill(0).map(it => [])
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
const cur = board[i][j];
// 空的无所谓,跳过,因为我们判断的是现有填上的是否合法
if (cur !== '.') {
// 当前横排已经有此元素,直接返回 false
if (rowsUsed[i].includes(cur)) {
return false
} else {
// 否则就填上,然后推入该已用数组
rowsUsed[i].push(cur)
}
// 竖排相同道理
if (colsUsed[j].includes(cur)) {
return false;
} else {
colsUsed[j].push(cur);
}
// 9个小方格稍微复杂点,算出大方块的 index,然后还是相同的判断9个数是否被用过
const boxIndex = Math.floor(i / 3) * 3 + Math.floor(j / 3);
if (boxesUsed[boxIndex].includes(cur)) {
return false;
} else {
boxesUsed[boxIndex].push(cur);
}
}
}
}
return true;
};
console.log(isValidSudoku(
[[".","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"]]
));
121. 解数独 (sudoku-solver)
标签
- DFS + 剪枝
- 困难
题目
这里不贴题了,leetcode打开就行,题目大意:
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) 数独部分空格内已填入了数字,空白格用 '.' 表示。
示例 1:
输入:board =
[["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"]]
输出:
[["5","3","4","6","7","8","9","1","2"],
["6","7","2","1","9","5","3","4","8"],
["1","9","8","3","4","2","5","6","7"],
["8","5","9","7","6","1","4","2","3"],
["4","2","6","8","5","3","7","9","1"],
["7","1","3","9","2","4","8","5","6"],
["9","6","1","5","3","7","2","8","4"],
["2","8","7","4","1","9","6","3","5"],
["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
基本思路
如果对 DFS + 回溯 问题不熟悉请先看看之前这个系列的问题
另外数独和之前的文章 N皇后 有点类似,可以先看看
这种题目基本都可以用暴力法求解,就是穷举,但是我们应该多思考更好的解决方案,如果你是个玩数独的高手,或者简单来说正常思维,我们都会在数独中,先找那种好填的
填写,什么是好填? 如果一行中,全都已经写好就差一个数没填,那是不是只能填那个数,你只有一种填法,只需要枚举一个数就行了,在深度遍历的路上相当于就一条道,简单吧。当然现实不可能这么简单,但思想就是,先从选择少的那些路开始走,这能大大减少 DFS 的复杂度,剪去了许多无用枝条
。
我们也可以做一次预处理,先把每个空格子能填的数都给他算好,然后同样 DFS 先遍历少的,然后回溯,最终找到一个正确解。详细做法根据我的代码注释可以清楚地理解,代码虽多,但我们的思路一定要清晰,不要畏惧,看懂了很简单。
写法实现
const solveSudoku = (board) => {
// 枚举值
const enumVal = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
const rows = new Array(9).fill(0).map(it => new Set(enumVal)); // 每一行的 可选数集
const cols = new Array(9).fill(0).map(it => new Set(enumVal)); // 每一列的 可选数集
const blocks = new Array(9).fill(0).map(it => new Set(enumVal)); // 每一子盒的 可选数集
// 根据坐标,获取所在的子盒子的索引
const getBoxIndex = (i, j) => {
return Math.floor(i / 3) * 3 + Math.floor(j / 3);
};
// 根据现有的已填的数字,精简可选数据集,把不能填写的数字从可选数据集中删除
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
if (board[i][j] !== ".") {
// 当前行出现过这个数字,这个数字就不能在这一行出现,就应该从这一行的`可选`数据集中删除
// 可选的意思就是 选了之后仍然合法
rows[i].delete(board[i][j]);
cols[j].delete(board[i][j]);
blocks[getBoxIndex(i, j)].delete(board[i][j]);
}
}
}
const dfs = (i, j) => {
// 列超过最边界坐标
if (j > 8) {
// 行+1
i++;
// 列回到起点
j = 0;
if (i > 8) {
// 都填完了
return true;
}
}
// 如果不是空白格,递归填下一格
if (board[i][j] !== ".") {
return dfs(i, j + 1)
}
const boxIndex = getBoxIndex(i, j); // 获取所在小框的索引
// 枚举出所有选择:1-9
for (let num = 1; num <= 9; num++) {
const cur = String(num);
// 当前选择必须在三个set中都存在,如果有一个不存在,就说明发生了冲突,跳过该选择,这就是剪枝
if (!rows[i].has(cur) || !cols[j].has(cur) || !blocks[boxIndex].has(cur)) continue;
// 作出选择,填上该数
board[i][j] = cur;
// 删掉这个可填选项
rows[i].delete(cur);
cols[j].delete(cur);
blocks[boxIndex].delete(cur);
if (dfs(i, j + 1)) return true; // 如果基于当前选择,填下一个,最后可解出数独,直接返回真
// 回溯,恢复为空白格
board[i][j] = ".";
// 将之前删掉的当前数字,加回来
rows[i].add(cur);
cols[j].add(cur);
blocks[boxIndex].add(cur);
}
// 尝试了1-9,每个都往下递归,都不能做完,返回false
return false;
};
// 填格子的起点
dfs(0, 0);
return board;
};
let board =
[["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"]]
console.log(solveSudoku(board))
另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧