LeetCode高频题37. 解数独

LeetCode高频题37. 解数独

提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
在这里插入图片描述

基础知识:
【1】LeetCode高频题36. 有效的数独


题目

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/sudoku-solver
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


一、审题

示例 1:
在这里插入图片描述

输入:board = [[“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”]]

输出:[[“5”,“3”,“4”,“6”,“7”,“8”,“9”,“1”,“2”],
[“6”,“7”,“2”,“1”,“9”,“5”,“3”,“4”,“8”],
[“1”,“9”,“8”,“3”,“4”,“2”,“5”,“6”,“7”],
[“8”,“5”,“9”,“7”,“6”,“1”,“4”,“2”,“3”],
[“4”,“2”,“6”,“8”,“5”,“3”,“7”,“9”,“1”],
[“7”,“1”,“3”,“9”,“2”,“4”,“8”,“5”,“6”],
[“9”,“6”,“1”,“5”,“3”,“7”,“2”,“8”,“4”],
[“2”,“8”,“7”,“4”,“1”,“9”,“6”,“3”,“5”],
[“3”,“4”,“5”,“2”,“8”,“6”,“1”,“7”,“9”]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

在这里插入图片描述

提示:

board.length == 9
board[i].length == 9
board[i][j] 是一位数字或者 ‘.’
题目数据 保证 输入数独仅有一个解


二、解题

当初我们通过直接判断的方式,解过本题
当时的解法就很简单
【1】LeetCode高频题36. 有效的数独

不过今天LeetCode要我们用填写数字的方式来解决这个题
其实复杂了点

但是也很简单,DFS剪枝的技巧是咱们经常学的,本题就是一个一个格子ij去填可能的数字,只要合法就行
本题要你填数字,但是不能违规,其实跟之前的36题几乎一模一样的

用一个递归函数去填
f(i,j)
(1)来到ij格子,如果是数字,绕过,因为有了数字了,下一次直接去下一个格子填
(2)如果ij是空格.,那还需要我们尝试0–9,看看能否放下呢?比如填num
自然需要检查这个行,这个列,这个九宫格是否一斤存在过num呢??
如果存在过,不好意思不能填写num,直接返回false,剪枝了就!!!
没有存在过,就可以填num
而且如果num填好了,后续所有格子在我ij是num的情况下,成功了,就成功,如果后续返回没有成功,说明我还需要尝试别的num
那就要恢复现场,把刚刚ij填好的那些标记,重新恢复原样,再填。

(3)填完,就要去下一个小格子填,要么是i,j+1格子
要么是i+1,0格子开始填
下次要去的i和j是这样推导的:
在这里插入图片描述

比如下图
看红色ij,填完,下次就去i,j+1,因为j还没有到8列
看绿色ij,填完,下次就去i+1,j=0格子,疑问j到了8列
任何时候,只要j没到8,i不动,j到了8,i需要+1去下一行
在这里插入图片描述
填的过程中,需要检查ij是所在行,列,格子是否出现过num,用row,column,ge仨标记,
他们仨什么含义,你看这个文章:【1】LeetCode高频题36. 有效的数独
我讲的很清楚了,本题就在这上面改编

(4)当i=9,说明0–8全部填好了,返回true

手撕f的代码很简单

public void solveSudoku(char[][] board) {
            //提前准备row, column, ge标记i行,j列,是否出现过num
            boolean[][] row = new boolean[9][10];//9行10个数字
            boolean[][] column = new boolean[9][10];//9行10个数字
            boolean[][] ge = new boolean[9][10];//9格10个数字

            //先把有数字那些地方标记了,待会有过的数字,不能再填了
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    int bid = 3*(i/3) + j/3;//转换ij为九宫格格子数
                    if (board[i][j] != '.'){
                        //这里头的数字还是要标记一下的
                        int num = board[i][j] - '0';//字符转数字
                        row[i][num] = true;
                        column[j][num] = true;
                        ge[bid][num] = true;
                    }
                }
            }

            //可以就打印board,不可以就不打印
            boolean ans = f(board, 0, 0, row, column, ge);
            if (ans){
                for (int i = 0; i < 9; i++) {
                    for (int j = 0; j < 9; j++) {
                        System.out.println(board[i][j] + ",");
                    }
                    System.out.println();
                }
            }
        }

        //递归函数f,带着row,column,ge仨标记,
        //用一个递归函数去填
        //f(i,j)
        public boolean f(char[][] arr, int i, int j,
                         boolean[][] row, boolean[][] column, boolean[][] ge){
            //(4)当i=9,说明0--8全部填好了,返回true
            if (i == 9) return true;
            //(3)填完,就要去下一个小格子填,要么是i,j+1格子
            //要么是i+1,0格子开始填
            int iNext = j == 8 ? i + 1 : i;
            int jNext = j == 8 ? 0 : j + 1;
            int bid = 3*(i/3) + j/3;//换算格子数

            //(1)来到ij格子,如果是数字,绕过,因为有了数字了,不管,下一次直接去下一个格子填
            if (arr[i][j] != '.') return f(arr, iNext, jNext, row, column, ge);
            else {
                //(2)如果ij是空格.,那还需要我们尝试0--9,看看能否放下呢?比如填num
                //自然需要检查这个行,这个列,这个九宫格是否一斤存在过num呢??
                for (int num = 1; num < 10; num++) {//没有0哟!!!
                    //如果存在过,不好意思不能填写num,直接返回false,剪枝了就!!!
                    if ((!row[i][num]) && (!column[j][num]) && (!ge[bid][num])){
                        //括号表示优先级,仨都得同时满足才能填
                        //没有存在过,就可以填num
                        //先标记
                        arr[i][j] = (char)(num + '0');//转字符
                        row[i][num] = true;
                        column[j][num] = true;
                        ge[bid][num] = true;
                        //然后去下一个格子看看,如果之后都行,直接返回true
                        boolean back = f(arr, iNext, jNext, row, column, ge);
                        if (back) return true;
                        //否则就要恢复现场,重新尝试新的num
                        arr[i][j] = '.';//恢复.字符
                        row[i][num] = false;
                        column[j][num] = false;
                        ge[bid][num] = false;
                    }
                }
                //如果9个数字,一个都没法填,都不行,直接剪枝,false
                return false;
            }
        }

注意,上述题目中

//否则就要恢复现场,重新尝试新的num
                        arr[i][j] = '.';//恢复.字符
                        row[i][num] = false;
                        column[j][num] = false;
                        ge[bid][num] = false;

目的就是为了尝试新的数字,你之前填a往后填不行的胡啊,此次不能填a,要换数字,所以需要恢复现场,这是DFS的骚操作

另外,题目准备好row,column和ge之后,先把arr检查一遍,把数字标记好
然后开始填表,用f

LeetCode测试;
在这里插入图片描述
按理说速度很快呀,不知道为啥如此慢?


总结

提示:重要经验:

1)本题要注意的关键就是同一行,同一列,同一九宫格,不能重复出现数字,1–9,压根不用0的
2)先将存在的数字标记好,然后填,如果尝试num发现不合法,剪枝,如果合法,填ij为num,同时看看后续的情况,后续OK,返回true,否则重新恢复现场,尝试新的num,如果1–9都尝试了,不行就false
3)注意下次去填写的位置iNext和jNext是如果动的,都看j,j没到8,i不动,j++,否则就是i+1,j=0
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值