已知空的 9 x 9 数独,
尝试将棋盘全部填满,填满后需要满足下述条件:
- 每行数字不能重复(一共9个位置,分别需要填入1-9,共9个数字)
- 每列数字不能重复(一共9个位置,分别需要填入1-9,共9个数字)
- 每个宫数字不能重复(每个33的子棋盘中,也就是上图中的粗实线,将99的棋盘切割成了9个3*3的子棋盘,每个子棋盘中,一共9个位置,分别需要填入1-9,共9个数字)
- 虚线圈起来区域(命名为:笼)里面的数字求和,需要等于笼左上方的数字小标(例如棋盘左上角的笼对应数字是11,则当前笼的两个空格对应的数字求和需要等于11。同一个笼内数字可重复)
对于此问题,已提供如下两个方法,可直接使用:
对于棋盘任意位置(i, j),得到当前所在笼的所需的数字和
int getLongTarget(i, j)
举例:假如棋盘左上角坐标为(0, 0),则getLongTarget(0, 0)返回11
对于棋盘任意位置(i, j),得到当前所在笼已填入数字的加和
int getLongCurrentSum(i, j)
举例:假如棋盘左上角坐标为(0, 0),第一行第二个空格的坐标为(0,1),且目前(0, 0)位置已填入3。则此时getLongCurrentSum(0, 1)返回3
分析:
根据数独中笼的位置,可以轻易算出
一排、一列、一宫 之和都是45
3排2列为6 ,(上2排所有笼之和 - 2 x 45)
3排7列为8 ,(右2列所有笼之和 - 2 x 45)
3排4列为7 ,(第5宫所有笼之和 - 1 x 45)
4排4列为3 。(根据4排4列所在笼 - 3排4列)
算到此处,其他的值就需要根据其他笼的值,进行排除了。
排除也是先看小值的笼,先看格子少的笼。
-
值小,意味着选择范围少。如笼的值为3的话,那么这两个格子只能为1或者2。
-
格子少,意味着更好排除。如笼的值为15的话,那么格子里的值不能小于6(因为如果小于6,另一个格子值就大于9了)
按照这个思路,在根据数独的特性就能得出结论了。
但是but,毕竟是做开发的,毕竟也是个程序员,运用机器来运算才是上策。
对于数独来说,最好的办法也是最笨的办法,那便是回溯。说白了,一个个去试,先把第一个格放1,然后下一个符不符合,符合在看下一个。。。不符合改为2。这样一个个试,如果试到9了,还不合适,那就无解了。
在写代码前,先吐槽两句:
因为当前数独引入了笼的概念,所以还需要考虑笼的限制。
可以说是限制,也说是我不太理解这个笼的定义,这个笼到底什么是变动的,笼的位置?还是笼的数字和?
- 如果都不能变,那代码写出来就不可变了呀,扩展性太差了。代码写出来就能解决当前笼的当前值。
- 如果笼的位置可变,那回溯的时候判断值合不合适的时候的跟谁相加?
- 如果笼的和可变,emmmmm,好像可以。
行了,上代码吧。(辛辛苦苦把提供的两个方法给写出来了,直呼好家伙~)
package com.chuai.site;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
public class ShuDu {
private int[][] arr ;
public ShuDu(){}
public ShuDu(int[][] arr){
this.arr = arr;
}
public int[][] getArr(){
return arr;
}
private HashSet[] rowSet = new HashSet[9];
private HashSet[] colSet = new HashSet[9];
private HashSet[] boxSet = new HashSet[9];
{
for(int i = 0;i<9;i++){
rowSet[i] = new HashSet();
colSet[i] = new HashSet();
boxSet[i] = new HashSet();
}
}
public static void main(String[] args) {
ShuDu shudu = new ShuDu(new int[9][9]);
long start = System.currentTimeMillis();
System.out.println("开始时间:"+ start);
boolean handle = shudu.handle(0);
long end = System.currentTimeMillis();
System.out.println("结束时间"+ end);
System.out.println("时间差:"+ (end-start) + "毫秒");
System.out.println("有解:" + handle);
System.out.println("=====================");
int[][] arr = shudu.getArr();
Arrays.stream(arr).forEach(a -> {
Arrays.stream(a).forEach( b -> {
System.out.print(b + " \t");
});
System.out.println();
});
}
public boolean handle(int index){
// 9*9 数独 最大索引 80
if (index > 80) { return true; }
// 根据索引计算坐标
int row = index/9;
int col = index%9;
// 如果当前位置为空,则获取当前位置合法值
if(arr[row][col] == 0){
// 获取所有合法值的集合,因为笼的存在,很多位置不一定 1-9
List<Integer> legalValues = getLegalValues(row, col);
for (int value : legalValues) {
//赋值 记录
rowSet[row].add(value);
colSet[col].add(value);
boxSet[getBoxIndex(row,col)].add(value);
arr[row][col] = value;
// 如果之后的值都合法,则当前值正确,否则恢复为0,继续遍历
if(handle(index+1)){
return true;
}else {
rowSet[row].remove(value);
colSet[col].remove(value);
boxSet[getBoxIndex(row,col)].remove(value);
arr[row][col] = 0;
}
}
return false;
}else { // 如果当前位置有值,则处理下一个位置
return handle(index+1);
}
}
/**
* 获取该位置的所有合法值
* @param row
* @param col
* @return
*/
private List<Integer> getLegalValues(int row, int col){
List<Integer> resultList = new ArrayList();
int max = getMax(row, col);
for(int i = 1;i<= max; i++){
if( isNoRepeat(row,col,i)){
resultList.add(i);
}
}
return resultList;
}
/**
* 根据行、列 获取宫的索引
* @param row
* @param col
* @return
*/
private int getBoxIndex(int row, int col){
return (row/3)*3 + (col/3);
}
/**
* 判断值是否与 行、列、宫 重复
* @param row
* @param col
* @param value
* @return true 值合法,不重复
* false 错误值,重复
*/
private boolean isNoRepeat(int row, int col, int value) {
if(row < 0 || row >= 9 || col < 0 || col >= 9 || value < 1 || value > 9 ){
// throw new RuntimeException();
return false;
}
//行
if(rowSet[row].contains(value)){
return false;
}
// 列
if(colSet[col].contains(value)){
return false;
}
// 宫
if(boxSet[getBoxIndex(row, col)].contains(value)){
return false;
}
// 合法值 需要符合笼的和
// 若能确定属于哪个笼,还可以筛选另一个加数大于10的值。
// 如一个笼2个元素,和为15 ,则 不可能为 1、2、3、4、5 否则另一个数将大于9
if((getLongTarget(row,col) - getLongCurrentSum(row,col)) < value){
return false;
}
return true;
}
/**
* 获取当前位置的最大值,根据 笼 或者 9
* 若笼的数字不重复,则可以确定笼中数字的个数 或者 笼的位置。
* 则 可以从小笼到大笼开始确定,遍历次数更少
* @param row
* @param col
* @return
*/
private int getMax(int row, int col){
int longTarget = getLongTarget(row, col);
int longCurrentSum = getLongCurrentSum(row, col);
int current = longTarget - longCurrentSum;
return (9 >= current) ? current : 9;
}
// 公有方法区。。。
}
好了,核心代码到这里就已经结束了,用的最笨的方式解决了这个数独,还有其他方法可以评论区讨论呀~
下面的这个方法,是题干中提供的公有方法,如果有想跑跑代码啥的可以复制到类里进行测试,不用写测试数据了。嚯嚯,不用谢,我叫磊疯,叫雷锋也行
/**
* 已提供方法 可直接使用
* 获取笼所需数字和
* @param i
* @param j
* @return
*/
private int getLongTarget(int i, int j){
if(i == 0){
if(j == 0 || j == 1){
return 11;
}else if(j == 2 || j == 3){
return 23;
}else if(j == 4){
return 12;
}else if(j == 5 || j == 6){
return 11;
}else if(j == 7 || j == 8){
return 14;
}
}else if(i == 1){
if(j == 0 || j == 1){
return 12;
}else if(j == 2 || j == 3){
return 23;
}else if(j == 4 || j == 5){
return 12;
}else if(j == 6){
return 11;
}else if(j == 7 || j == 8){
return 13;
}
}else if(i == 2){
if(j == 0 ){
return 6;
}else if(j == 1){
return 12;
}else if(j == 2){
return 3;
}else if(j == 3){
return 10;
}else if(j == 4 || j == 5){
return 13;
}else if(j == 6 || j == 7){
return 15;
}else if(j == 8){
return 11;
}
}else if(i == 3){
if(j == 0 ){
return 6;
}else if(j == 1){
return 13;
}else if(j == 2){
return 3;
}else if(j == 3){
return 10;
}else if(j == 4 || j == 5){
return 15;
}else if(j == 6 ){
return 8;
}else if(j == 7){
return 15;
}else if(j == 8){
return 11;
}
}else if(i == 4){
if(j == 0 ){
return 12;
}else if(j == 1){
return 13;
}else if(j == 2){
return 10;
}else if(j == 3 || j == 4){
return 9;
}else if( j == 5){
return 3;
}else if(j == 6 ){
return 8;
}else if(j == 7){
return 8;
}else if(j == 8){
return 11;
}
}else if(i == 5){
if(j == 0 ){
return 12;
}else if(j == 1){
return 14;
}else if(j == 2){
return 10;
}else if(j == 3 || j == 4){
return 15;
}else if( j == 5){
return 3;
}else if(j == 6 ){
return 12;
}else if(j == 7){
return 8;
}else if(j == 8){
return 11;
}
}else if(i == 6){
if(j == 0 || j == 1){
return 14;
}else if(j == 2){
return 23;
}else if(j == 3 || j == 4){
return 11;
}else if( j == 5){
return 14;
}else if(j == 6 ){
return 12;
}else if(j == 7){
return 10;
}else if(j == 8){
return 5;
}
}else if(i == 7){
if(j == 0 || j == 1){
return 9;
}else if(j == 2 || j == 3){
return 23;
}else if(j == 4){
return 6;
}else if( j == 5){
return 14;
}else if(j == 6 ){
return 19;
}else if(j == 7){
return 10;
}else if(j == 8){
return 5;
}
}else if(i == 8){
if(j == 0 || j == 1){
return 13;
}else if(j == 2 || j == 3){
return 23;
}else if(j == 4){
return 6;
}else if( j == 5 || j == 6){
return 19;
}else if(j == 7 || j == 8){
return 11;
}
}
return -1 ;
}
/**
* 已提供方法 可直接使用
* 获取笼已填入数字和
* @param i
* @param j
* @return
*/
private int getLongCurrentSum(int i, int j){
if(i == 0 && (j == 0 || j == 1)) {
return arr[0][0] + arr[0][1];
}
if((i == 1 && (j == 0 || j == 1)) || (i == 2 && j == 1)) {
return arr[1][0] + arr[1][1] + arr[2][1];
}
if((i == 2 || i == 3 )&& j == 0) {
return arr[2][0] + arr[3][0];
}
if((i == 4 || i == 5 )&& j == 0) {
return arr[4][0] + arr[5][0];
}
if((i == 3 || i == 4 )&& j == 1) {
return arr[3][1] + arr[4][1];
}
if((i == 5 && j == 1) || (i == 6 && (j == 0 || j == 1))) {
return arr[5][1] + arr[6][0] + arr[6][1];
}
if(i == 7 && (j == 0 || j == 1)) {
return arr[7][0] + arr[7][1];
}
if(i == 8 && (j == 0 || j == 1)) {
return arr[8][0] + arr[8][1];
}
if( (i == 0 && (j == 2 || j == 3)) || (i == 1 && (j == 2 || j == 3)) ){
return arr[0][2]+arr[0][3]+arr[1][2]+arr[1][3];
}
if((i == 2 || i == 3) && j == 2){
return arr[2][2]+arr[3][2];
}
if((i == 2 || i == 3) && j == 3){
return arr[2][3]+arr[3][3];
}
if((i == 4 || i == 5) && j == 2){
return arr[4][2]+arr[5][2];
}
if(i == 4 && (j == 3 || j == 4)){
return arr[4][3]+arr[4][4];
}
if(i == 5 && (j == 3 || j == 4)){
return arr[5][3]+arr[5][4];
}
if(i == 6 && (j == 3 || j == 4)){
return arr[6][3]+arr[6][4];
}
if((i == 6 && j == 2) || ((i == 7 || i == 8) && (j == 2 || j == 3)) ){
return arr[6][2]+arr[7][2] +arr[7][3] +arr[8][2]+arr[8][3];
}
if( ((i == 0 || i == 1) && j == 4) || (i == 1 && j == 5) ){
return arr[0][4]+arr[1][4] +arr[1][5] ;
}
if(i == 2 && (j == 4 || j == 5)){
return arr[2][4]+arr[2][5];
}
if(i == 3 && (j == 4 || j == 5)){
return arr[3][4]+arr[3][5];
}
if((i == 4 || i == 5) && j == 5){
return arr[4][5]+arr[5][5];
}
if((i == 6 || i == 7) && j == 5){
return arr[6][5]+arr[7][5];
}
if((i == 7 || i == 8) && j == 4){
return arr[7][4]+arr[8][4];
}
if((i == 0 && (j == 5 || j == 6)) || (i == 1 && j == 6)) {
return arr[0][5] + arr[0][6] + arr[1][6];
}
if(i == 0 && (j == 7 || j == 8)) {
return arr[0][7] + arr[0][8];
}
if(i == 1 && (j == 7 || j == 8)) {
return arr[1][7] + arr[1][8];
}
if((i == 2 && (j == 6 || j == 7)) || (i == 3 && j == 7)) {
return arr[2][6] + arr[2][7] + arr[3][7];
}
if((i == 2 || i == 3) && j == 8){
return arr[2][8]+arr[3][8];
}
if((i == 4 || i == 5) && j == 8){
return arr[4][8]+arr[5][8];
}
if((i == 3 || i == 4) && j == 6){
return arr[3][6]+arr[4][6];
}
if((i == 5 || i == 6) && j == 6){
return arr[5][6]+arr[6][6];
}
if((i == 4 || i == 5) && j == 7){
return arr[4][7]+arr[5][7];
}
if((i == 6 || i == 7) && j == 7){
return arr[6][7]+arr[7][7];
}
if((i == 6 || i == 7) && j == 8){
return arr[6][8]+arr[7][8];
}
if(i == 8 && (j == 7 || j == 8)) {
return arr[8][7] + arr[8][8];
}
if((i == 7 && j == 6) || (i == 8 && (j == 5 || j == 6))) {
return arr[7][6] + arr[8][5] + arr[8][6];
}
return -1;
}
这回所有代码都结束了,真没有了,别翻了。。。