在线数独,这个网站有解答
https://www.sudoku.name/index-cn.php
算法思路
使用二维数组表示棋盘,0表示空
先计算出每个空可能的值的集合
依次进行dfs搜索,每次确定一个值之后,将该值所在的九宫和横竖所有集合进行更新,即减去该值
直至搜索结束
优化思路
使用一维数组表示棋盘,每个数字0-9位表示可能的值的集合,10位表示该值是否是空
使用位运算加速
同时依赖了是否为空,可以只对空进行搜索,避免多个空删除一个值后变为单个数时无法确定是否是空,必须使用check函数的问题
const _ = require('lodash')
// 0 表示未知
const INIT_MAP =
// 难
// [
// [8, 0, 0, 0, 0, 0, 0, 0, 0],
// [0, 0, 3, 6, 0, 0, 0, 0, 0],
// [0, 7, 0, 0, 9, 0, 2, 0, 0],
// [0, 5, 0, 0, 0, 7, 0, 0, 0],
// [0, 0, 0, 0, 4, 5, 7, 0, 0],
// [0, 0, 0, 1, 0, 0, 0, 3, 0],
// [0, 0, 1, 0, 0, 0, 0, 6, 8],
// [0, 0, 8, 5, 0, 0, 0, 1, 0],
// [0, 9, 0, 0, 0, 0, 4, 0, 0]
// ]
// [ //容易
// [8, 2, 0, 5, 0, 0, 0, 0, 7],
// [4, 0, 0, 0, 0, 6, 8, 0, 0],
// [0, 5, 7, 8, 0, 0, 6, 0, 0],
// [0, 0, 0, 3, 0, 0, 0, 2, 4],
// [7, 0, 0, 4, 0, 1, 0, 0, 0],
// [0, 8, 4, 0, 2, 0, 1, 0, 0],
// [9, 0, 0, 0, 3, 0, 0, 5, 8],
// [0, 7, 0, 0, 8, 0, 9, 0, 6],
// [0, 0, 8, 0, 5, 2, 0, 7, 0]
// ]
// [ //容易
// [7, 4, 8, 1, 2, 3, 6, 5, 9],
// [2, 6, 3, 4, 9, 5, 1, 8, 7],
// [1, 5, 9, 6, 7, 8, 2, 4, 3],
// [4, 1, 5, 3, 6, 7, 9, 2, 8],
// [3, 8, 2, 9, 5, 4, 7, 6, 1],
// [9, 7,6, 8, 1, 2, 4, 3, 5],
// [5, 2, 4, 7, 3, 9, 8, 1, 6],
// [8, 9, 1, 5, 4, 6, 3, 7, 2],
// [6, 3, 7, 2, 8, 1, 5, 9, 4]
// ]
//
// [ //容易
// [7, 4, 0, 1, 2, 0, 6, 5, 0],
// [2, 6, 3, 4, 9, 5, 1, 8, 7],
// [0, 5, 9, 6, 7, 8, 0, 4, 3],
// [0, 1, 0, 0, 0, 0, 0, 2, 8],
// [0, 8, 2, 0, 0, 4, 0, 6, 1],
// [0, 7, 6, 0, 0, 0, 0, 3, 0],
// [0, 2, 4, 0, 3, 0, 8, 1, 6],
// [8, 9, 0, 5, 4, 0, 3, 7, 2],
// [0, 3, 0, 2, 0, 1, 5, 9, 0]
// ]
// [ //容易
// [0, 0, 0, 1, 2, 3, 6, 5, 0],
// [0, 0, 0, 4, 9, 5, 1, 8, 0],
// [0, 0, 0, 6, 7, 8, 2, 4, 0],
// [4, 1, 5, 3, 6, 7, 0, 0, 0],
// [3, 8, 2, 9, 5, 4, 7, 6, 1],
// [9, 7, 6, 8, 1, 2, 4, 3, 5],
// [0, 2, 4, 7, 3, 9, 8, 1, 0],
// [0, 9, 1, 5, 4, 6, 3, 7, 0],
// [0, 0, 0, 2, 8, 1, 0, 0, 0]
// ]
// [ // 容易
// [0, 9, 0, 6, 0, 0, 3, 0, 0],
// [0, 3, 0, 0, 5, 0, 0, 2, 8],
// [5, 0, 1, 0, 0, 2, 7, 0, 0],
// [0, 0, 6, 1, 0, 8, 0, 0, 2],
// [0, 1, 0, 0, 0, 0, 0, 3, 0],
// [8, 0, 0, 5, 0, 9, 6, 0, 0],
// [0, 0, 9, 7, 0, 0, 4, 0, 3],
// [1, 4, 0, 0, 2, 0, 0, 9, 0],
// [0, 0, 8, 0, 0, 6, 0, 7, 0]
// ]
// 7,9,2,6,8,1,3,4,5
// 6,3,4,9,5,7,1,2,8
// 5,8,1,3,4,2,7,6,9
// 4,7,6,1,3,8,9,5,2
// 9,1,5,2,6,4,8,3,7
// 8,2,3,5,7,9,6,1,4
// 2,6,9,7,1,5,4,8,3
// 1,4,7,8,2,3,5,9,6
// 3,5,8,4,9,6,2,7,1
[
[0, 0, 5, 0, 0, 4, 0, 7, 0],
[9, 0, 0, 0, 0, 7, 0, 0, 0],
[0, 0, 3, 0, 0, 0, 2, 0, 0],
[0, 5, 0, 0, 8, 0, 0, 9, 0],
[0, 0, 0, 0, 0, 3, 0, 0, 0],
[0, 7, 0, 0, 0, 0, 0, 2, 0],
[0, 0, 2, 0, 0, 0, 8, 0, 0],
[0, 0, 0, 5, 0, 0, 0, 0, 4],
[0, 6, 0, 9, 0, 0, 5, 0, 0],
]
// 1,2,5,3,6,4,9,7,8
// 9,4,6,8,2,7,1,5,3
// 7,8,3,1,5,9,2,4,6
// 2,5,4,6,8,1,3,9,7
// 6,1,9,2,7,3,4,8,5
// 3,7,8,4,9,5,6,2,1
// 5,3,2,7,4,6,8,1,9
// 8,9,1,5,3,2,7,6,4
// 4,6,7,9,1,8,5,3,2
function range(n = 9) {
return Array(n).fill(0).map((_, i) => i + 1)
}
// console.log(getSet(INIT_MAP, 0, 8))
// 根据一个状态,计算某个点的可能数字集合
// 改点必须为确定的数字
function getSet(m, x, y) {
// 确定的数字
if (1 < m[x][y] && m[x][y] <= 9)
return new Set([m[x][y]])
// 一个数字的集合,返回该集合的拷贝
if (m[x][y].size === 1)
return new Set([...m[x][y]]);
// 默认9个数,然后去除不合法的
let s = new Set(range(9))
for (let i = 0; i < 9; i++) {
if (i === x) continue
// 去除列中的重复数
if (m[i][y] > 0 && m[i][y] <= 9)
s.delete(m[i][y])
if (m[x][i].size === 1)
s.delete(...m[i][y])
}
for (let i = 0; i < 9; i++) {
if (i === y) continue
// 去除行的重复数
if (m[x][i] > 0 && m[x][i] <= 9)
s.delete(m[x][i])
if (m[x][i].size === 1)
s.delete(...m[x][i])
}
// 去除九宫格中的重复数
let stx = Math.floor(x / 3) * 3// 左上角x
let sty = Math.floor(y / 3) * 3// 左上角y
for (let i = 0; i < 3; i++)
for (let j = 0; j < 3; j++) {
let nx = stx + i
let ny = sty + j
let v = m[nx][ny]
// console.log(nx, ny)
if (v > 0 && v <= 9)
s.delete(v)
if (v.size === 1)
s.delete(...v)
}
return s
}
// 根据初始状态,获取每个位置可能的数字集合
function init(m) {
let res = Array(9).fill(0).map(
() => Array(9).fill(0)
)
for (let i = 0; i < 9; i++)
for (let j = 0; j < 9; j++) {
if (0 < m[i][j] && m[i][j] <= 9) {
// 如果是数字
res[i][j] = new Set([m[i][j]])
} else {
// 如果是未知
res[i][j] = getSet(m, i, j)
}
}
return res
}
// 深度拷贝一个对象
function copy(obj) {
return _.cloneDeep(obj)
}
// 删除m,x,y相关的数,横纵,九宫格
function delSet(m, x, y, v) {
// 删除 行和列中的数字
for (let i = 0; i < 9; i++) {
m[i][y].delete(v)
m[x][i].delete(v)
}
// 去除九宫格中的重复数
let stx = Math.floor(x / 3) * 3// 左上角x
let sty = Math.floor(y / 3) * 3// 左上角y
for (let i = 0; i < 3; i++)
for (let j = 0; j < 3; j++) {
let nx = stx + i
let ny = sty + j
m[nx][ny].delete(v)
}
m[x][y] = new Set([v])
for (let i = 0; i < 9; i++)
for (let j = 0; j < 9; j++)
if (m[i][j].size === 0) return false
// true 表示可以是该数字
// 细致的检查
return check(m)
}
// 检测是否满足要求
function check(m) {
// console.log(toStr(m))
// let rows = m.map(x => new Set(x))
// let cols = []
for (let i = 0; i < 9; i++) {
let c = new Set()
let r = new Set()
for (let j = 0; j < 9; j++) {
let colVal = m[i][j]
let rowVal = m[j][i]
if (colVal.size === 1) {
if (c.has(...colVal))
return false
c.add(...colVal)
}
if (rowVal.size === 1) {
if (r.has(...rowVal))
return false
r.add(...rowVal)
}
}
// console.log('check', c, r)
}
// 九宫格
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let s = new Set()
for (let x = 0; x < 3; x++) {
for (let y = 0; y < 3; y++) {
let nx = i * 3 + x
let ny = j * 3 + y
let v = m[nx][ny]
if (v.size > 1) continue
if (s.has(...v))
return false
s.add(...v)
}
}
}
}
return true
// let f1 = rows.every(x => x.size === 9)
// let f2 = cols.every(x => x.size === 9)
//
// return f1 && f2
}
function toStr(m) {
// console.log(m)
let s = m.map(
row => row.map(x => [...x].join(''))
)
return s.join('n')
}
// 从x,y 开始找一个需要填补的位置,即集合的尺寸大于1
function getPos(m, x, y) {
// console.log('getPos', x, y)
for (let i = 0; i < 9; i++)
for (let j = 0; j < 9; j++) {
if (i * 9 + j < x * 9 + y) continue
// console.log('getPos', i, j, m[i][j])
if (m[i][j].size > 1)
return [i, j]
}
// 没有的话表示搜索已经完成
return [9, 9]
}
// 深度搜索
// 由m开始,x,y坐标进行搜索
// 每个坐标中是一个可能的数的集合
function dfs(m, x, y, deep = 0) {
console.log('dfs', x, y, deep)
// console.log('---dfs')
// console.log(toStr(m))
// console.log('---dfsn')
if (x === 9 && y === 9) {
// console.log('---end')
// console.log(toStr(m))
// console.log('---endn')
// 搜索到最后一个格子时表示搜索结束了
for (let i = 0; i < 9; i++)
for (let j = 0; j < 9; j++) {
if (m[i][j].size !== 1) return false
}
return m
}
let p = getPos(m, x, y)
// console.log('getPos', p)
let [i, j] = p
let v = m[i][j]
// 非跳过的数字,在集合中去除一个数字后,修改相关所有集合
for (let delVal of v) {
let mm = copy(m)
let f = delSet(mm, i, j, delVal)
console.log('delVal', i, j, delVal, 'n', toStr(mm))
// 不能选择改数字
if (!f) continue
let [nx, ny] = getPos(mm, i, j)
// console.log('nx,ny', nx, ny)
let res = dfs(mm, nx, ny, deep + 1)
if (res)
return res
}
return false
}
function main() {
let m = init(INIT_MAP)
console.log(toStr(m))
console.log('---nnn---')
let res = dfs(m, 0, 0)
if (res)
console.log(toStr(res))
// let res = dfs(m, 0, 0)
// console.log(res)
// console.log(init(m))
}
main()
来源:oschina
链接:https://my.oschina.net/ahaoboy/blog/4295871