• 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;
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    注意,上述题目中

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

    目的就是为了尝试新的数字,你之前填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,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 相关阅读:
    tiworker.exe是什么程序?tiworker.exe占用大量内容如何解决?
    字符串去掉()以及()中的文字
    用户画像图谱
    怎么提取一个python文件中所有得函数名称
    Adaptive AUTOSAR 学习笔记 7 - 应用设计和 Manifest
    Linux通用基线配置
    大浪淘沙,自动驾驶迎来下半场
    蓝桥等考C++组别一级010
    python常见错误类型
    用 NativeScript 开发 iOS 应用,如何调试?
  • 原文地址:https://blog.csdn.net/weixin_46838716/article/details/125453034