力扣每日一题,37. 解数独
做了不少回溯题了,渐渐不看题解也能靠自己的思路通过,今天的数独题自己也能独立通过,虽然速度不快,不过也说明自己对回溯的理解是正确到位的。
题目描述
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
空白格用 '.'
表示。
一个数独。
答案被标成红色。
Note:
- 给定的数独序列只包含数字
1-9
和字符'.'
。 - 你可以假设给定的数独只有唯一解。
- 给定数独永远是
9x9
形式的。
思路
用回溯法,和N皇后问题差不多。
回溯法介绍
- 回溯就是把问题的解空间转化成了图或者树的结构表示,然后使用深度优先遍历,遍历过程中记录和寻找所有可行解或者最优解。
- 如果完成一件事情有很多种方法,并且每一种方法分成若干步骤,那多半就可以使用回溯算法完成。
- 基本思想是“尝试搜索”,一条路如果走不通(不能得到想要的结果),就回到上一个“路口”,尝试走另一条路。
回溯法的模板
void backtrace(int i, int n, other parameters) {
if (i == n) {
//获取一个答案
return;
}
//一般子结点有多少个就循环多少次
for (){
backtrack(i + 1, n, other parameters);
}
}
回溯法思路其实很"暴力",对这道题,我具体思路是,把数独的所有空格取出来,然后1将放进第一个空格里,若1已存在(在它所在行或所在列或所在的3x3宫内已经有了),就放2,若2也有了就放3…直到这个数放进第一个空格,
接着第二个空格也从1开始放进去,直到放进去的数符合要求。。。同理再放第三个空格、第四个空格。。。
从这个思路可见,这真的很黄很暴力
个人觉得想理解好回溯法,去刷力扣全排列(传送门)那道题挺好。
提交代码
我将空格抽象为一个Blank类,属性有空格的值,在数独中的横纵坐标。
package 解数独;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
public class Solution {
@Test
public void testSolveSudoku() throws Exception {
char nums[][] = { { '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' } };
solveSudoku(nums);
for (int j = 0; j < nums.length; j++) {
System.out.println(Arrays.toString(nums[j]));
}
}
public void solveSudoku(char[][] board) {
char chars[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
List<Blank> blanks = new ArrayList<Solution.Blank>();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] == '.') {
blanks.add(new Blank('.', i, j));
}
}
}
char res[][] = new char[9][9];
backtrace(0, blanks.size(), chars, blanks, board, res);
for (int j = 0; j < board.length; j++) {
board[j] = Arrays.copyOf(res[j], res[j].length);
}
}
private void backtrace(int i, int n, char chars[], List<Blank> blanks,
char nums[][], char res[][]) {
if (i == n) {
for (int j = 0; j < nums.length; j++) {
res[j] = Arrays.copyOf(nums[j], nums[j].length);
}
return;
}
for (int j = 0; j < chars.length; j++) {
Blank temp = blanks.get(i);
temp.val = chars[j];
if (!isUsed(temp.val, temp.x, temp.y, nums)) {
nums[temp.x][temp.y] = temp.val;
backtrace(i + 1, n, chars, blanks, nums, res);
nums[temp.x][temp.y] = '.';
temp.val = '.';
}
}
}
class Blank {
char val;
int x;
int y;
public Blank(char val, int x, int y) {
this.val = val;
this.x = x;
this.y = y;
}
}
private boolean isUsed(char a, int x, int y, char nums[][]) {
for (int i = 0; i < 9; i++) {
if (i == x)
continue;
if (nums[i][y] == a) {
// System.out.println(i+" "+y);
return true;
}
}
for (int j = 0; j < 9; j++) {
if (j == y)
continue;
if (nums[x][j] == a) {
// System.out.println(x+" "+j);
return true;
}
}
int istart = 0, jstart = 0, iend = 3, jend = 3;
if (x > 2 && x < 6) {
istart = 3;
iend = 6;
} else if (x > 5) {
istart = 6;
iend = 9;
}
if (y > 2 && y < 6) {
jstart = 3;
jend = 6;
} else if (y > 5) {
jstart = 6;
jend = 9;
}
for (int i = istart; i < iend; i++) {
for (int j = jstart; j < jend; j++) {
if (nums[i][j] == a) {
// System.out.println(i+" "+j);
return true;
}
}
}
return false;
}
}
分享本
算法竞赛入门经典
https://o8.cn/ztnkDg
密码:rdu3
(本地高速是下载器)