package com.example.springboot01;
import org.junit.Test;
import org.junit.platform.commons.util.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Test0805 {
/**
* 解数独
* @param board 待解数独
*/
public void solveSudoku(char[][] board) {
// 存储备选数字,全部都是null时,表示数独完全解析出来了
String[][] newBoard = new String[9][9];
// 统计备选数字
solveSudoku1(board, newBoard);
// 使用规则1和规则2解数独
boolean isContinue = true;
while (isContinue) {
isContinue = solveSudoku2(board, newBoard) || solveSoduko3(board, newBoard);
}
// 暴力破解数独, 破解速度和备选数字的先后排序有很大关联
int result = checkSodukoState(newBoard);
if (2 == result) {
solveSudoku4(0, 0, board, newBoard);
}
printStringArr(newBoard);
}
/**
* 统计备选数字
* @param board 待解数独
* @param newBoard 剩余备选数字
*/
public void solveSudoku1(char[][] board, String[][] newBoard) {
// 初始化备选数字
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if ('.' == board[i][j]) {
newBoard[i][j] = "123456789";
}
}
}
// 从备选数字中排除掉确定数组
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if ('.' != board[i][j]) {
cleaNum(i, j, board[i][j]+"", newBoard);
}
}
}
}
/**
* 使用待选数字排除法,直到待选数字只剩一个
* @param board 待解数独
* @param newBoard 剩余备选数字
*/
public boolean solveSudoku2(char[][] board, String[][] newBoard) {
boolean isContinue = false;
// 使用待选数字排除法,直到待选数字只剩一个
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if ('.' == board[i][j] && newBoard[i][j].length() == 1) {
char item = newBoard[i][j].charAt(0);
board[i][j] = item;
newBoard[i][j] = null;
isContinue = true;
// 清除备选数字
cleaNum(i, j, item + "", newBoard);
}
}
}
return isContinue;
}
/**
* 清除行,列,3*3单元格内的指定备选数字
* @param i 行
* @param j 列
* @param item 待清除数字
* @param newBoard 备选数字
*/
private void cleaNum(int i, int j, String item, String[][] newBoard) {
// 清除备选数字
// 3*3单元格起始坐标(左上角)
int blockRow = (i / 3) * 3;
int blockCol = (j / 3) * 3;
for (int m = 0; m < 9; m++) {
// 清除掉行里面的备选数字item
if (newBoard[i][m] != null) {
newBoard[i][m] = newBoard[i][m].replace(item, "");
}
// 清除掉列里面的备选数字item
if (newBoard[m][j] != null) {
newBoard[m][j] = newBoard[m][j].replace(item, "");
}
// 清除掉3*3单元格里面的备选数字item
int x = blockRow + (m/3);
int y = blockCol + (m%3);
if (newBoard[x][y] != null) {
newBoard[x][y] = newBoard[x][y].replace(item, "");
}
}
}
/**
* 一个3*3单元格,一行或者一列的所有备选数字中,如果某个数只出现了一次,那么这个数就是最终数字
* @param board 待解数独
* @param newBoard 剩余备选数字
* @return 是否成功解开一个数字
*/
private boolean solveSoduko3(char[][] board, String[][] newBoard) {
boolean isContinue = false;
// 行
for (int i = 0; i < 9; i++) {
Map<Character, Integer> numToRegion = new HashMap<>();
Map<Character, Integer> map = new HashMap<>();
for (int j = 0; j < 9; j++) {
if (newBoard[i][j] != null) {
char[] chars = newBoard[i][j].toCharArray();
for (char item : chars) {
numToRegion.put(item, j);
if (map.containsKey(item)) {
map.put(item, map.get(item) + 1);
} else {
map.put(item, 1);
}
}
}
}
for (char item : map.keySet()) {
if (map.get(item) == 1) {
isContinue = true;
int j = numToRegion.get(item);
board[i][j] = item;
newBoard[i][j] = null;
// 清除备选数字
cleaNum(i, j, item+"", newBoard);
}
}
}
// 列
for (int j = 0; j < 9; j++) {
Map<Character, Integer> numToRegion = new HashMap<>();
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < 9; i++) {
if (newBoard[i][j] != null) {
char[] chars = newBoard[i][j].toCharArray();
for (char item : chars) {
numToRegion.put(item, i);
if (map.containsKey(item)) {
map.put(item, map.get(item) + 1);
} else {
map.put(item, 1);
}
}
}
}
for (char item : map.keySet()) {
if (map.get(item) == 1) {
isContinue = true;
int i = numToRegion.get(item);
board[i][j] = item;
newBoard[i][j] = null;
// 清除备选数字
cleaNum(i, j, item+"", newBoard);
}
}
}
// 3*3单元格,一共9个
for (int x = 0; x < 9; x++) {
int row = (x/3)*3;
int col = (x%3)*3;
// 存储每个备选数字对应的坐标
Map<Character, String> numToRegion = new HashMap<>();
// 存储备选数字的个数
Map<Character, Integer> map = new HashMap<>();
for (int i = row; i < row+3; i++) {
for (int j = col; j < col+3; j++) {
if (newBoard[i][j] != null) {
char[] chars = newBoard[i][j].toCharArray();
for (char item : chars) {
numToRegion.put(item, i + "," + j);
if (map.containsKey(item)) {
map.put(item, map.get(item) + 1);
} else {
map.put(item, 1);
}
}
}
}
}
for (char item : map.keySet()) {
// 如果备选个数为1,那么就填入这个数字
if (map.get(item) == 1) {
String[] location = numToRegion.get(item).split(",");
int i = Integer.parseInt(location[0]);
int j = Integer.parseInt(location[1]);
board[i][j] = item;
newBoard[i][j] = null;
isContinue = true;
// 清除备选数字
cleaNum(i, j, item+"", newBoard);
}
}
}
return isContinue;
}
/**
* 暴力拆除法,依次尝试备选数字
* @param board1 待解数独
* @param newBoard1 剩余备选数字
* @return 处理结果,返回 1 表示解数独完成,返回 2 表示没有解, 返回 3 表示备份数字错误,需要继续尝试下一个
*/
private int solveSudoku4(int startRow, int startCol, char[][] board1, String[][] newBoard1) {
// 复制
char[][] board = new char[9][9];
String[][] newBoard = new String[9][9];
for (int m = 0; m < 9; m++) {
for (int n = 0; n < 9; n++) {
board[m][n] = board1[m][n];
newBoard[m][n] = newBoard1[m][n];
}
}
// 随机选择一个备选数字
for (int i = startRow; i < 9; i++) {
for (int j = startCol; j < 9; j++) {
if (StringUtils.isNotBlank(newBoard[i][j])) {
int length = newBoard[i][j].length();
for (int k = 0; k < length; k++) {
char item = newBoard[i][j].charAt(k);
board[i][j] = item;
newBoard[i][j] = null;
cleaNum(i, j, item+"", newBoard);
boolean isContinue2 = true;
while (isContinue2) {
isContinue2 = solveSudoku2(board, newBoard) || solveSoduko3(board, newBoard);
}
int result = checkSodukoState(newBoard);
if (1 == result) {
// 复制最终结果
for (int m = 0; m < 9; m++) {
for (int n = 0; n < 9; n++) {
board1[m][n] = board[m][n];
newBoard1[m][n] = newBoard[m][n];
}
}
return 1;
} else if (2 == result) {
int result2 = solveSudoku4(i, j, board, newBoard);
if (1 == result2) {
for (int m = 0; m < 9; m++) {
for (int n = 0; n < 9; n++) {
board1[m][n] = board[m][n];
newBoard1[m][n] = newBoard[m][n];
}
}
return 1;
}
}
// 当最后一个备份数字也不符合时,需要修改前面一位备份数字
if (k == length-1 && result == 3) {
return 3;
}
// 回退到上一步,尝试下一个字符
for (int m = 0; m < 9; m++) {
for (int n = 0; n < 9; n++) {
board[m][n] = board1[m][n];
newBoard[m][n] = newBoard1[m][n];
}
}
}
}
}
}
return 3;
}
/**
* 判断数独状态
* 1.newBoard值全为null, 表示全部解开
* 2.newBoard里面有数字, 表示未完全解开
* 3.newBoard里面没有数字,存在空字符串,表示数独不存在唯一解
* @param newBoard 备选数字
* @return 数独状态
*/
private int checkSodukoState(String[][] newBoard) {
boolean isAllNull = true;
for (int i=0; i<9; i++) {
for (int j=0; j<9; j++) {
if (newBoard[i][j] != null) {
isAllNull = false;
if ("".equals(newBoard[i][j])) {
return 3;
}
}
}
}
return isAllNull ? 1 : 2;
}
private final String[] ten = new String[]{"1","2","3","4","5","6","7","8","9"};
private String getRestNum(String str) {
String retStr = "";
for (String item : ten) {
if (!str.contains(item)) {
retStr += item;
}
}
return retStr;
}
private void printArr(char[][] chars) {
System.out.println("===============================================================================");
for (int i=0; i<9; i++) {
for (int j=0; j<9; j++) {
System.out.print(chars[i][j] + "\t");
}
System.out.println();
}
System.out.println("===============================================================================");
}
private void printStringArr(String[][] strs) {
System.out.println("===============================================================================");
for (int i=0; i<9; i++) {
for (int j=0; j<9; j++) {
System.out.print(strs[i][j] + "\t");
}
System.out.println();
}
System.out.println("===============================================================================");
}
@Test
public void testLikou() {
Set<Integer> temp = new HashSet<>();
temp.add(1);
temp.add(2);
temp.remove(1);
//temp.remove(2);
long startTime = System.currentTimeMillis();
int n=8;
int k=8;
if (k > n) {
return;
}
if (k == 0) {
return;
}
//combine4(n, k);
//System.out.println(allCombineList.size());
/*char[][] chars = new char[][]{
{'.','.','9','7','4','8','.','.','.'},
{'7','.','.','.','.','.','.','.','.'},
{'.','2','.','1','.','9','.','.','.'},
{'.','.','7','.','.','.','2','4','.'},
{'.','6','4','.','1','.','5','9','.'},
{'.','9','8','.','.','.','3','.','.'},
{'.','.','.','8','.','3','.','2','.'},
{'.','.','.','.','.','.','.','.','6'},
{'.','.','.','2','7','5','9','.','.'}};*/
/*char[][] chars = new char[][]{
{'5','.','2','.','6','.','.','.','.'},
{'.','.','.','4','.','.','.','8','2'},
{'.','.','.','.','.','2','.','.','1'},
{'.','.','.','8','.','.','.','5','.'},
{'.','.','3','.','.','.','6','.','.'},
{'.','7','.','.','.','9','.','.','.'},
{'8','.','.','2','.','.','.','.','.'},
{'2','4','.','.','.','1','.','.','.'},
{'.','.','.','.','3','.','2','.','7'}};*/
/*char[][] chars = new char[][]{
{'.','.','8','.','.','.','2','.','.'},
{'.','3','.','8','.','2','.','6','.'},
{'7','.','.','.','9','.','.','.','5'},
{'.','5','.','.','.','.','.','1','.'},
{'.','.','4','.','.','.','6','.','.'},
{'.','2','.','.','.','.','.','7','.'},
{'4','.','.','.','8','.','.','.','6'},
{'.','7','.','1','.','3','.','9','.'},
{'.','.','1','.','.','.','8','.','.'}};*/
// 专家级
/*char[][] chars = new char[][]{
{'8','.','6','.','5','.','3','.','.'},
{'.','5','.','.','.','4','.','.','2'},
{'4','.','.','.','.','.','.','1','.'},
{'.','.','.','3','.','.','2','.','9'},
{'7','.','5','.','.','.','.','.','.'},
{'.','.','.','.','8','1','.','.','.'},
{'.','.','8','2','.','.','9','.','.'},
{'.','.','.','4','.','.','.','.','8'},
{'.','2','.','.','6','3','.','.','.'},};*/
/*char[][] chars = new char[][]{
{'6','.','.','.','.','.','5','.','.'},
{'.','1','.','.','.','2','.','.','4'},
{'.','.','7','.','3','.','.','6','.'},
{'.','.','.','.','.','4','.','2','.'},
{'.','.','.','.','6','1','.','.','.'},
{'9','3','.','.','.','.','4','8','.'},
{'.','8','.','7','1','.','.','9','.'},
{'.','2','.','.','.','.','.','.','7'},
{'7','.','.','.','5','.','.','.','.'},};*/
// 世上最难数独
char[][] chars = new char[][]{
{'8','.','.','.','.','.','.','.','.'},
{'.','.','3','6','.','.','.','.','.'},
{'.','7','.','.','9','.','2','.','.'},
{'.','5','.','.','.','7','.','.','.'},
{'.','.','.','.','4','5','7','.','.'},
{'.','.','.','1','.','.','.','3','.'},
{'.','.','1','.','.','.','.','6','8'},
{'.','.','8','5','.','.','.','1','.'},
{'.','9','.','.','.','.','4','.','.'}};
/*char[][] chars = new char[][]{
{'.','.','.','.','5','.','.','3','.'},
{'.','.','.','.','4','.','.','.','.'},
{'.','9','.','.','.','.','.','.','2'},
{'.','.','.','.','.','.','.','.','.'},
{'6','.','.','.','.','.','.','4','.'},
{'.','.','.','8','.','2','.','.','7'},
{'5','.','.','.','.','.','.','.','.'},
{'3','.','4','.','6','.','.','.','.'},
{'.','.','.','7','.','.','2','.','9'}};*/
solveSudoku(chars);
printArr(chars);
System.out.println("耗时: " + (System.currentTimeMillis() - startTime));
}
}
力扣解数独