Leetcode37题,解数独

13 篇文章 0 订阅
8 篇文章 0 订阅

leetcode 37题,自动解数独

老婆听说我在研究自动解数独,赞叹地说这是不是人工智能啊。咳咳,脸红中,其实没那么玄乎,就是一道算法题,只不过其题材是大家喜闻乐见的数独而已。
2013年时,那时还在工行,刚海外调回来,工作上比较空,且那时候有个大新闻,一个中国农民解出了“史上最难数独”,我也跃跃欲试。
于是鼓捣了一个工作日,用C写了一个算法出来,自测通过并发表到内部技术论坛上。为啥不用熟悉的Java?那时工作机性能不行,打开一个当前要维护的银行Java项目已经有不流畅的感觉,我实在不想再开一个Java IDE进程,用记事本写Java又不太习惯。后来是用C在UltraEdit里面写好并用gcc编译的。

leetcode 37题也是数独题目
在这里插入图片描述
解出来后是如下:
在这里插入图片描述


解题思路

数独的解题思路有两种:

一种是正向递推的“消元法”

模拟人脑解数独的方法,就是统计每个单元格横向,竖向,宫格这三个维度中其他数字,并将本单元格[1-9]这9种可能性减去其他数字,剩下如果只有一个数字,那就是本单元格确定的数字,这种方法不需要回溯,但是只能应对简单级别的数独题目。
这种方法下的测试案例

[["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"]]

另一种是反向解题的“递归法”

从map取一个待定的点,遍历其set范围,当某值时,设置board,删map,然后递归下一步,发现不行要回退。注意这里的关键是“回退”,就是上一个单元格尝试性地定个值(可能后来发现不合适,这个值会被推翻),这个单元格在此基础上继续尝试,如果哪一步发现尝试不下去了,就回退掉该单元格取值。
这种方法下的测试案例

[[".",".","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",".","."]]

消元法解决数独问题的Java代码

package com.zzz.life;
import java.util.*;
public class Sudo {
    class Point {
        int i; //二维数组下标
        int j; //二维数组下标
        int id; //所在方格id,比如11区
        Point(int i, int j) {
            this.i = i;
            this.j = j;
            this.id = (i<3?1:(i<6?2:3))*10 + (j<3?1:(j<6?2:3));
        }
        @Override
        public boolean equals(Object obj) {
            return obj instanceof Point && (this.i==((Point)obj).i && this.j==((Point)obj).j);
        }
        @Override
        public int hashCode() {
            return (i+j+id)%1024;
        }
    }
    private static final int SIZE = 9;
    private Set<Character> cloneSet() {
        final char[] possibility = new char[]{'1','2','3','4','5','6','7','8','9'};
        Set<Character> ret = new HashSet<>();
        for (char i : possibility) {
            ret.add(i);
        }
        return ret;
    }
    private void setInit(char[][] board, Map<Point, Set<Character>> map, Queue<Point> q) {
        map.clear();
        q.clear();
        for (int i=0; i<SIZE; i++) {
            for (int j=0; j<SIZE; j++) {
                Point tmp = new Point(i,j);
                if (board[i][j] == '.') {
                    map.put(tmp, cloneSet());
                } else {
                    q.offer(tmp);
                }
            }
        }
        System.out.println(map.size() + "|" + q.size());
    }
    private void demention(char[][] board, Map<Point, Set<Character>> map, Queue<Point> q) {
        Point tmp = q.poll();
        if (map.containsKey(tmp)) map.remove(tmp);
        char posval = board[tmp.i][tmp.j];
        for (Point point : map.keySet()) {
            if (point.i == tmp.i || point.j == tmp.j || point.id == tmp.id) {
                Set<Character> posset = map.get(point);
                posset.remove(posval);
                if (posset.size() == 1) {
                    q.offer(point);
                    char c = posset.iterator().next();
                    board[point.i][point.j] = c;
                }
            }
        }
    }
    private boolean check(char[][] board, Point point, char val) {
        for (int i=0; i<SIZE; i++) {
            for (int j=0; j<SIZE; j++) {
                Point tmp = new Point(i, j);
                if (tmp.i==point.i || tmp.j==point.j || tmp.id == point.id) {
                    if (tmp.i == point.i && tmp.j == point.j) return true;
                    if (board[tmp.i][tmp.j] == val) {
                        System.out.println(tmp.i +"|"+tmp.j);
                        return false;
                    }
                }
            }
        }
        return true;
    }
    public boolean recusive(char[][] board, Map<Point, Set<Character>> map, Iterator<Point> iterator) {
        Point p = iterator.next();
        System.out.println("not finished yet! ["+ p.i +"|"+ p.j + "]");
        Set<Character> set = map.get(p);
        for (char c : set) {
            board[p.i][p.j] = c;
            if (check(board, p, c) && (iterator.hasNext() && recusive(board, map, iterator))) {
                return true;
            } else {
                board[p.i][p.j] = '.';
            }
        }
        return false;
    }
    public void solveSudoku(char[][] board) {
        //思路:降维法,将用‘。’表示的不可能性,变为1-9的概率数组
        //1.建立可能矩阵,已有条件进入降维队列
        //2.利用降维队列中内容,循环减少可能矩阵的可能性
        //3.期间发现size为1的,降维,该点进入降维队列,作为下一轮的弹药
        //如此循环直到所有点都降维了
        Map<Point, Set<Character>> map = new HashMap<>();
        Queue<Point> q = new LinkedList<>();
        setInit(board, map, q);
        //降维
        while (!q.isEmpty()) {
            demention(board, map, q);
        }
        //后来发现需要猜,所以到最后如果可能矩阵还是有未降维的,则需要逐一尝试;
        //采用递归机制,从map取一个待定的点,遍历其set范围,当某值时,设置board,删map,然后递归下一步,发现不行要回退
        //如果map中只有一个待定点了,遍历check就行了
        while (map.size() > 0) {
            System.out.println("not finished yet! " + map.size());
            Iterator<Point> iterator = map.keySet().iterator();
            recusive(board, map, iterator);
        }
    }
    public static void main(String[] args) {
        char[][] board = 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','.','.'}
        };
        Sudo solution = new Sudo();
        solution.printBoard(board);
        solution.solveSudoku(board);
        solution.printBoard(board);
    }
    private void printBoard(char[][] board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();
        }
    }
}

后来终于找到了我在2013年写的c的数独解法(递归法)

在leetcode上同样编译测试通过

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/timeb.h>

int shudu[10][10];     //保存数独,使用时下标从1开始   
int tt[10][10][10];    //保存数独可能取值,数独每点对应这里一组
FILE* flog;            //日志文件
FILE* fshudu;          //读取数独题目所在文件

//给数独数组的有值节点赋值
int init_shudu(void);
//初始化数独数组对应的三维数组
int init_tt(int x, int y);
//打印数独和记运行时间
void print_shudu(void);
//数独求解函数,采用递归方式从上到下从左到右求解
int looper(int x, int y);
//判断数独某单元格值没有重复性
int no_repeat(int x, int y, int value);

int main(int argc, char* argv[])
{
	int i, j, ret;
	fopen_s(&flog, "mylog.txt","w");
	printf("开始进行数独求解!----------------\n");
	fprintf(flog, "开始进行数独求解!----------------\n");
	
	ret = init_shudu(); 
	if (ret != 0) goto Error;
	ret = init_tt(0, 0); 
	if (ret != 0) goto Error;
	print_shudu();
	ret = looper(1, 1); 
	if (ret != 0) goto Error;
	print_shudu();
	
	//对解出的数独进行逐格校验
	for (i=1; i<=9; i++) 
		for (j=1; j<=9; j++) 
			if (no_repeat(i, j, shudu[i][j]) != 0) 
				fprintf(flog, "检查发现单元[%d,%d]值不对!\n",i,j);
	printf("数独求解结束!----------------\n");
	fprintf(flog, "数独求解结束!----------------\n");
	fclose(flog);
	printf("请按回车键退出...");
	getchar();
	return 0;
Error:
	fclose(flog);
	printf("程序发生异常终止!请检查日志!\n");
	printf("请按回车键退出...");
	getchar();
	return ret;
}

/**初始化数独数组(9*9 矩阵)有值单元格,无值则为0 */
int init_shudu(void) {
	int i, j, num, cnt = 0;

	for(i=1; i<=9; i++)
		for(j=1; j<=9; j++) shudu[i][j] = 0;

	if (fopen_s(&fshudu, "myshudu.txt","r") != 0) {
		printf("打开数独文件报错!请检查myshudu.txt!\n");
		fprintf(flog, "打开数独文件报错!请检查myshudu.txt!\n");
		return -1;
	}
	for (i=1; i<=9; i++) {
		for (j=1; j<=9; j++) {
			cnt += fscanf_s(fshudu, "%d ", &num);
			if (num<0 || num>9) return -1;
			else shudu[i][j] = num;
		}
	}
	if (cnt != 81) return -1;
	fclose(fshudu);
	return 0;
}

/**
 @ 根据数独数组初始化对应的三维数组(除了传入参数点对应的数组)
 @ 参数:保留点的坐标,该点对应的数组不被初始化;若要全初始化,传入[0,0]
*/
int init_tt(int x, int y) {
	int i, j, k;
	for(i=1; i<=9; i++)
		for(j=1; j<=9; j++) {
			if (x==i && y==j) continue;
			if (shudu[i][j] != 0) for(k=1; k<=9; k++) tt[i][j][k] = -1;
			else for(k=1; k<=9; k++) tt[i][j][k] = 0;
		}
	return 0;
}

/**数独求解函数,采用递归方式从上到下从左到右求解
 @ 参数:当前节点在数独的坐标位置;
 @ 返回值:0 成功 -1 无解 -2 位置错误
 */
int looper(int x, int y) {
	int k, nexti, nextj;
	//参数检查
	if (x<1 || x>9 || y<1 || y>9) return -2;
	//计算下一步的单元格的坐标
	if (y==3 || y==6 || y==9) {
		if (x == 9) { nexti = 1; nextj = y+1; }
		else { nexti = x+1; nextj = y-2; }
	} else { nexti = x; nextj = y+1; }
	//单元格有初始值或已被赋值时
	if (shudu[x][y] != 0) {
		if (x==9 && y==9) {
			printf("完成数独解算过程!\n");
			return 0; 
		} else {
			return looper(nexti, nextj);
		}
	} 
	//单元格没有值时需要进行试值
	for (k=1; k<=9; k++) {
		if (tt[x][y][k] == -1) continue;
		else {
			tt[x][y][k] = -1;
			if (no_repeat(x, y, k) == 0) {
				int tmpi;
				shudu[x][y] = k;
				fprintf(flog,"位置[%d,%d] := %d\n", x, y, k);
				if (x==9 && y==9) {
					printf("在对最后一格赋值后,完成数独解算过程!\n");
					return 0; 
				}
				//对所在的行、列筛除				
				for (tmpi=1; tmpi<=9; tmpi++) {
					tt[x][tmpi][k] = -1;
					tt[tmpi][y][k] = -1;

				}
				//对所在的3*3小方阵筛除
				if (x<=3 && y<=3) { tt[1][1][k] = -1; tt[1][2][k] = -1; tt[1][3][k] = -1; tt[2][1][k] = -1; tt[2][2][k] = -1; tt[2][3][k] = -1; tt[3][1][k] = -1; tt[3][2][k] = -1; tt[3][3][k] = -1; }
				else if (x<=3 && y<=6) { tt[1][4][k] = -1; tt[1][5][k] = -1; tt[1][6][k] = -1; tt[2][4][k] = -1; tt[2][5][k] = -1; tt[2][6][k] = -1; tt[3][4][k] = -1; tt[3][5][k] = -1; tt[3][6][k] = -1; }
				else if (x<=3 && y<=9) { tt[1][7][k] = -1; tt[1][8][k] = -1; tt[1][9][k] = -1; tt[2][7][k] = -1; tt[2][8][k] = -1; tt[2][9][k] = -1; tt[3][7][k] = -1; tt[3][8][k] = -1; tt[3][9][k] = -1; }
				else if (x<=6 && y<=3) { tt[4][1][k] = -1; tt[4][2][k] = -1; tt[4][3][k] = -1; tt[5][1][k] = -1; tt[5][2][k] = -1; tt[5][3][k] = -1; tt[6][1][k] = -1; tt[6][2][k] = -1; tt[6][3][k] = -1; }
				else if (x<=6 && y<=6) { tt[4][4][k] = -1; tt[4][5][k] = -1; tt[4][6][k] = -1; tt[5][4][k] = -1; tt[5][5][k] = -1; tt[5][6][k] = -1; tt[6][4][k] = -1; tt[6][5][k] = -1; tt[6][6][k] = -1; }
				else if (x<=6 && y<=9) { tt[4][7][k] = -1; tt[4][8][k] = -1; tt[4][9][k] = -1; tt[5][7][k] = -1; tt[5][8][k] = -1; tt[5][9][k] = -1; tt[6][7][k] = -1; tt[6][8][k] = -1; tt[6][9][k] = -1; }
				else if (x<=9 && y<=3) { tt[7][1][k] = -1; tt[7][2][k] = -1; tt[7][3][k] = -1; tt[8][1][k] = -1; tt[8][2][k] = -1; tt[8][3][k] = -1; tt[9][1][k] = -1; tt[9][2][k] = -1; tt[9][3][k] = -1; }
				else if (x<=9 && y<=6) { tt[7][4][k] = -1; tt[7][5][k] = -1; tt[7][6][k] = -1; tt[8][4][k] = -1; tt[8][5][k] = -1; tt[8][6][k] = -1; tt[9][4][k] = -1; tt[9][5][k] = -1; tt[9][6][k] = -1; }
				else if (x<=9 && y<=9) { tt[7][7][k] = -1; tt[7][8][k] = -1; tt[7][9][k] = -1; tt[8][7][k] = -1; tt[8][8][k] = -1; tt[8][9][k] = -1; tt[9][7][k] = -1; tt[9][8][k] = -1; tt[9][9][k] = -1; }					
				if (looper(nexti, nextj) != 0) {  //递归,如果下一步不成功则前功尽弃,需要回退
					int tmpk;
					shudu[x][y] = 0;
					init_tt(x, y);
					for (tmpk=k+1; tmpk<=9; tmpk++) tt[x][y][tmpk] = 0; //调试,回退本列上未尝试部分
				} else return 0;                                        //如果下一步成功,就跳出对k的遍历
			} //对应 if (no_repeat(x, y, k) 的判断,如果这个试值不行,就去试下个值
		} // if (tt[x][y][k]
	} //for
	return -1;
}

/**传入参数是当前节点在数独的位置,和试图的取值;
 @ 注意:根据数独的规则,合法的取值应该满足行、列、小方阵都没有重复
 @ 返回值:0 可以取该值 -1 不可取该值 -2 位置错误 -3 试图赋的值错误
*/
int no_repeat(int x, int y, int value) {
	int tmp;
	int ipos, jpos, m, n;
	if (x<1 || x>9 || y<1 || y>9) return -2;
	if (value<1 || value>9) return -3;
	for (tmp=1; tmp<=9; tmp++) 
		if (y!=tmp && (shudu[x][tmp]==value)) return -1; 
	for (tmp=1; tmp<=9; tmp++) 
		if (x!=tmp && (shudu[tmp][y]==value)) return -1; 
	if (x==1 || x==4 || x==7) ipos = 0;
	if (x==2 || x==5 || x==8) ipos = 1;
	if (x==3 || x==6 || x==9) ipos = 2;
	if (y==1 || y==4 || y==7) jpos = 0;
	if (y==2 || y==5 || y==8) jpos = 1;
	if (y==3 || y==6 || y==9) jpos = 2;
	for (m=0; m<3; m++)
		for (n=0; n<3; n++) 
			if ((m!=ipos) && (n!=jpos) && (shudu[x-ipos+m][y-jpos+n]==value)) 
				return -1;
	return 0;
}

void print_shudu(void) {
	int i, j;
	struct timeb tp1;
	struct tm *s_tm1;

	//打印这个数独题目
	for(i=1; i<=9; i++) {
		for(j=1; j<=9; j++) printf("%d ", shudu[i][j]);
		printf("\n");	
	}
	printf("---------------------\n");
	
	//记录交易时间
	ftime(&tp1);
	s_tm1 = localtime(&(tp1.time));
	printf("Start  :  %02d:%02d:%02d\n\n", s_tm1->tm_hour, s_tm1->tm_min, s_tm1->tm_sec);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值