数独游戏

题目描述

图片说明

输入格式

The input test file will contain multiple cases. Each test case consists of a single line containing 81 characters, which represent the 81 squares of the Sudoku grid, given one row at a time. Each character is either a digit (from 1 to 9) or a period (used to indicate an unfilled square). You may assume that each puzzle in the input will have exactly one solution. The end-of-file is denoted by a single line containing the word “end”.
注意: 本题输入数据量较大,cin, getline可能会超时,建议使用scanf。

输出格式

For each test case, print a line representing the completed Sudoku puzzle.

输入样例

.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end

输出样例

527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293746185864351297647913852359682714128574936

题目思路

题目整体思路很容易想到:对于每一个空位置,尝试每一个可填数,填入后与同行、同列和同块的所有数字进行比对,看是否有重复;若有,更改当前填入数据,直到该位置无可能性,返回上一个填写的位置,更改数据。

难点1 怎样实现与同行同列同块数字的比较?

首先,我们可以通过一维数组存储输出输入的字符数组。这会为后面的遍历和查找带来方便。对该数组进行扩充,变成二维数组left,第一维存放该位置的数字,第二维存放该位置可放数字的可能性(如果数字确定可能性就为1)。
该位置可放数字的可能性通过函数assign对数组的更新来确定

bool assign(int left[][10],int idx,int lefted)
{
    //假设idx位置的值是lefted 
    left[idx][0]=1;
    for(int i=1;i<10;i++)
    {
        left[idx][i]=0;
    }
    left[idx][lefted]=1;

    int row=idx/9,col=idx%9;
    //在相应行判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=row*9+i;
        //若当前判断位置不是自身且该位置lefted是唯一可能性,说明该假设错误 
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }    
    }
    //在相应列判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=i*9+col;
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }
    }
    //在相应块判断有无重复元素 
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            int now=((row/3)*3+i)*9+(col/3)*3+j;
            if(now!=idx&&!remove(left,now,lefted))
            {
                return false;
            }
        }
    }
    return true;
}

难点2 怎样修改某位置的可能数字?

通过remove函数实现,注意,修改时要对可能性为1和2的位置进行特判

//从value数组中移除 value[idx][lefted]的可能性 
bool remove(int left[][10],int idx,int lefted)
{ 
    if(left[idx][lefted]==1)//若shudu[idx]有取lefted的可能 
    {
        left[idx][lefted]=0;
        left[idx][0]--;
        //该位置没有其他可能性了
        if(left[idx][0]==0)
        {
            return false;
        }
        //该位置只有一种可能性了
        else if(left[idx][0]==1)
        { 
            for(int i=1;i<10;i++)
            {
                if(left[idx][i]!=0)
                {
                    if(!assign(left,idx,i))
                        return false;
                    else
                        return true;
                }
            }    
        }
        else
            return true; 
    }
    else 
        return true; 
}

难点3 怎样修改递归函数使得时间复杂度减低?

在选择试探的空位时,我们可以采用每次优先选择可能数字最少的进行试探,这样可以在极大程度上减少我们的搜索量。(快速失败法)

//选择shudu[]的空缺位置中可能性最少的位置进行匹配 
bool search_match(int left[][10])
{    
    int minx=0,minl=9;
    int flag=1;
    for(int i=0;i<81;i++)
    {
        if(left[i][0]!=1)
        {
            flag=0;
            if(left[i][0]<minl)
            {
                minl=left[i][0];
                minx=i;
            }
        }
    }
    //当所有位置的可能性都为1时说明成功,结束递归 
    if(flag==1)
    {
        complete_shudu(left);
        return true;
    }

    for(int i=1;i<10;i++)
    {

        //遍历该位置的所有可能性 
        if(left[minx][i]!=0)
        {
            //设临时数组,将left[][]拷贝过来  
            int left_temp[81][10];
            for(int j=0;j<81;j++)
            {
                for(int k=0;k<10;k++)
                {
                    left_temp[j][k]=left[j][k];
                }
            }
            //若i可以成功插入,且i插入后的数独可以完整填出来 
            if(assign(left_temp,minx,i))
            {
                if(search_match(left_temp))
                    return true;                
            }
            //若i不可以插入,则在left数组中删掉该可能性 
            left[minx][0]--;
            left[minx][i]=0;

        }    
    }
    return false;
}

完整代码

#include<stdio.h>
#include<string.h>

char shudu[100];//输入的数独数组
int left[82][10]={0};//对于数独第i号格子shudu[i][0]表示其可选择的数,shudu[i][j]表示是否可选数字j 
bool assign(int value[][10],int idx,int lefted);

//对left数组进行初始化
void init(int left[82][10],char s[100])
{
    for(int i=0;i<81;i++)
    {
        if(s[i]=='.')
        {
            left[i][0]=9;
            for(int j=1;j<10;j++)
            {
                left[i][j]=1;
            }
        }
        else
        {
            left[i][0]=1;
            for(int j=1;j<10;j++)
            {
                left[i][j]=0;
            }
            left[i][(int)(s[i]-'0')]=1;
        }
    }
}
//打印数独数组
void print(char s[100])
{
    for(int i=0;i<81;i++)
    {
        printf("%c",s[i]);
    }
    printf("\n");
}
//将整理好的数字填充到shudu数组中
void complete_shudu(int left[][10])
{
    for(int i=0;i<81;i++)
    {
        for(int j=1;j<10;j++)
        {
            if(left[i][j]!=0)
            {
                shudu[i]=j+'0';
                break;
            }
        }
    }
}
//从value数组中移除 value[idx][lefted]的可能性 
bool remove(int left[][10],int idx,int lefted)
{ 
    if(left[idx][lefted]==1)//若shudu[idx]有取lefted的可能 
    {
        left[idx][lefted]=0;
        left[idx][0]--;
        if(left[idx][0]==0)
        {
            return false;
        }
        else if(left[idx][0]==1)
        { 
            for(int i=1;i<10;i++)
            {
                if(left[idx][i]!=0)
                {
                    if(!assign(left,idx,i))
                        return false;
                    else
                        return true;
                }
            }    
        }
        else
            return true; 
    }
    else 
        return true; 
}
//更新shudu[idx]的值 ,并更新left表 
bool assign(int left[][10],int idx,int lefted)
{
    //假设idx位置的值是lefted 
    left[idx][0]=1;
    for(int i=1;i<10;i++)
    {
        left[idx][i]=0;
    }
    left[idx][lefted]=1;

    int row=idx/9,col=idx%9;
    //在相应行判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=row*9+i;
        //若当前判断位置不是自身且该位置lefted是唯一可能性,说明该假设错误 
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }    
    }
    //在相应列判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=i*9+col;
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }
    }
    //在相应块判断有无重复元素 
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            int now=((row/3)*3+i)*9+(col/3)*3+j;
            if(now!=idx&&!remove(left,now,lefted))
            {
                return false;
            }
        }
    }
    return true;
}
//选择shudu[]的空缺位置中可能性最少的位置进行匹配 
bool search_match(int left[][10])
{    
    int minx=0,minl=9;
    int flag=1;
    for(int i=0;i<81;i++)
    {
        if(left[i][0]!=1)
        {
            flag=0;
            if(left[i][0]<minl)
            {
                minl=left[i][0];
                minx=i;
            }
        }
    }
    //当所有位置的可能性都为1时说明成功,结束递归 
    if(flag==1)
    {
        complete_shudu(left);
        return true;
    }

    for(int i=1;i<10;i++)
    {

        //遍历该位置的所有可能性 
        if(left[minx][i]!=0)
        {
            //设临时数组,将left[][]拷贝过来  
            int left_temp[81][10];
            for(int j=0;j<81;j++)
            {
                for(int k=0;k<10;k++)
                {
                    left_temp[j][k]=left[j][k];
                }
            }
            //若i可以成功插入,且i插入后的数独可以完整填出来 
            if(assign(left_temp,minx,i))
            {
                if(search_match(left_temp))
                    return true;                
            }
            //若i不可以插入,则在left数组中删掉该可能性 
            left[minx][0]--;
            left[minx][i]=0;

        }    
    }
    return false;
}

int main()
{
    scanf("%s",shudu);
    while(strcmp(shudu,"end")!=0)
    {
        //每次读入一个数独,都要将left数组重置 
        memset(left,0,sizeof(left));
        init(left,shudu);
        //根据已确定的数字更新未确定块的可选情况
        for(int i=0;i<81;i++)
        {
            if(shudu[i]!='.')
            {
                assign(left,i,(int)(shudu[i]-'0'));
            } 
        } 

        search_match(left);

        print(shudu);
        memset(shudu,NULL,sizeof(shudu));
        scanf("%s",shudu);
    }
    return 0;
}

总结

数独的解法中,最经典的是舞蹈链的解法,可以很好的降低时间复杂度,但是目前自己还看不懂这种做法。
链接先放在这里,以后再慢慢看:https://www.cnblogs.com/grenet/p/3163550.html
另,特别感谢我的小伙伴耐心讲解,这里附上他的博客地址:https://www.cztcode.com/2020/poj3074-summary/

全部评论

相关推荐

04-16 10:27
已编辑
美团_Saas_后端开发
今天周一休息,突发奇想写一篇阶段总结。如题,我已经去了一个和Java彻底毫无关联的行业。曾经我以为自己能在计算机行业发光发热,拿到美团offer那会感觉自己天都亮了。没想到刚入行一年多就当了逃兵。从最开始的热爱到现在一看到代码就厌恶,不知道自己经历了什么。所以我去干什么了?答案是:在成都当了租房销售。上班那会压力大了就念叨着去干租房中介,但是一直下不去这个决心,想着自己学了四年多的计算机知识,终究还是不甘心。终于在某一天准备八股文的时候,看着无数篇和工作内容关系不大的理论知识,那一刻下定决心,决定尝试一下销售行业,也算是给自己一个交代。后面阴差阳错的投了成都自如去当租房管家,没想到面试很顺利,在当天一百多个面试的人里面,我成为了为数不多通过的几个幸运儿之一。目前已经培训通过,正式入职,也开了单,有压力但是每天过得很开心,真心喜欢那种和人交流的感觉,哪怕是最后没有选择找我租房。说这些也是想告诉那些大三,大四正在找Java实习而焦虑的同学:你们现在还年轻,选择很多,容错率也很高,可以尽情去尝试自己喜欢的行业和工作。不用因为某一次的面试没通过或者简历石沉大海而焦虑,更不用因为身边人都在挤编程的独木桥就强迫自己跟风。也算是自己的碎碎念吧,也希望自己能在新的领域取得一点小成就。也祝牛油工作顺利!
沉淀小子:干啥都不丢人啊,生存是必须要的,销售很考验一个人综合素质能力的,好的销售人脉和资源可不比写字楼的白领差啊
点赞 评论 收藏
分享
03-03 23:12
已编辑
北京邮电大学 Java
书海为家:我来给一点点小建议,因为毕竟还在学校不像工作几年的老鸟有丰富的项目经验,面试官在面试在校生的时候更关注咱们同学的做事逻辑和思路,所以最好在简历中描述下自己做过项目的完整过程,比如需求怎么来的,你对需求的解读,你想到的解决办法,遇到困难如何找人求助,最终项目做成了什么程度,你从中收获了哪些技能,你有什么感悟。
你的简历改到第几版了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务