力扣日记3.8-【回溯算法篇】37. 解数独

力扣日记:【回溯算法篇】37. 解数独

日期:2023.3.8
参考:代码随想录、力扣

37. 解数独

题目描述

难度:困难

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

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

示例 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] 是一位数字或者 ‘.’
  • 题目数据 保证 输入数独仅有一个解

题解

cpp ver
class Solution {
private:
    bool isValid(vector<vector<char>>& board, int row, int col, char c) {
        // 同一行
        for (int i = 0; i < 9; i++) {
            if (board[row][i] == c) return false;
        }
        // 同一列
        for (int i = 0; i < 9; i++) {
            if (board[i][col] == c) return false;
        }
        // 确定九宫格的位置
        int x = col / 3;    // 是商而不是余数!!!
        int y = row / 3;
        for (int j = y * 3; j < y * 3 + 3; j++) {
            for (int i = x * 3; i < x * 3 + 3; i++) {
                if (board[j][i] == c)   return false;
            }
        }
        return true;
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
    // 返回值:只需要找到一个解即可,所以用bool
    // 参数:board
    bool backtracking(vector<vector<char>>& board) {
        // 在当前层递归,通过两层for循环找到当前需要填数字的位置(空格)
        for (int row = 0; row < 9; row++) {
            for (int col = 0; col < 9; col++) {
                // 找到空位置
                if (board[row][col] == '.') {
                    // 如果为空,开始填数字,此时才是所谓的for循环横向遍历
                    for (char c = '1'; c <= '9'; c++) { // 啊啊啊!这里不小心写成 < '9' 直接导致不能找到答案!!!导致最后棋盘是空的
                        // 如果有效,则处理节点并递归
                        if (isValid(board, row, col, c)) {
                            board[row][col] = c;
                            // 递归(进入下一个棋盘,从头开始找空格)
                            // 这里用if接受返回结果,如果下一层递归遍历完整个棋盘都没有空格(则返回true),这里也直接返回
                            if (backtracking(board))    return true;
                            // 否则进行回溯
                            board[row][col] = '.';
                        }
                    }
                    // 如果9个数都不行,说明之前层的递归中放的数字有问题,则返回false
                    return false;
                }
            }
        }
        // 如果在当前层递归遍历完一整个棋盘都没有空格,说明填满了,返回true
        return true;
    }
#endif
};

复杂度

时间复杂度:
空间复杂度:

思路总结

  • 本题与之前做的Q皇后,甚至组合、子集等问题,在思路上完全不一致。
    • 前面的题目,在递归时是有指明递归方向的(纵向遍历),如Q皇后中,递归参数row + 1指明递归是往下一行遍历,组合、子集问题中递归参数startindex = i + 1 也指明递归是往下一个值遍历。
    • 而本题中,进入下一层递归时,并没有指明方向,而是直接给一整个棋盘,至于如何知道当前层递归需要处理的是哪个位置,则通过最开始的两层for循环来找位置。
    • 硬要说的话,可能排列问题中,在每次递归时也是直接给一整个集合,然后在下一层递归从头开始遍历(再通过used去重)。这种思路可能有点类似。但是本题除了通过for循环找到当前位置外,还要在当前位置再去进行一层横向遍历(遍历数字1-9),所以相比前者,多了一个维度。)
  • 因此,每层递归的这两个for循环,是为了找到当前这层递归,是轮到哪一个位置放数字了。一个位置一个位置看是否已经有值了,如果是空的,才进行所谓的“横向遍历”(看1-9哪一个数字有效)。再如果找到了有效的数字,就进行处理节点和递归(又从头开始找哪一个位置放数字)。
    • 有效数字要满足三个条件:
      • 同一行不能重复
      • 同一列不能重复
      • 同一个九宫格不能重复(要先确定在哪个九宫格)
  • 至于什么时候终止,则是等最后一层递归时,发现自己遍历完棋盘上所有位置都没有空格了,就返回true表示都填满了。然后退出当前层递归,会由上一层递归捕获到其返回的true,再继续退出。因此,不需要专门的终止条件。
  • 至于回溯,则是当前层递归如果试了9个数字都不行,则说明前面层的递归所放的数字是存在问题的,则返回false退出当前层递归,再由上一层递归捕获到false后进行回溯。
  • 由于需要这个true和false来判断是否终止,因此返回值则需要一个bool值(而不是之前的void)。
    • 用bool还是void与之前树的题目类似,也是看是需要一个结果(一条边)即可,还是要多个结果(所有边)
  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
力扣是一个在线编程平台,提供了大量的算法题目,可以帮助程序员提高算法能力。回溯算法是一种搜索算法,它通过不断地尝试所有可能的来求问题。在回溯算法中,我们首先定义一个空间,然后从空间中搜索所有可能的,直到找到符合要求的为止。回溯算法通常用于求组合问题、排列问题、子集问题等。 在 Java 中实现回溯算法,通常需要定义一个递归函数来搜索空间。在递归函数中,我们首先判断当前状态是否符合要求,如果符合要求,则将当前状态加入到集中;否则,我们继续搜索下一个状态。在搜索下一个状态时,我们需要对当前状态进行一些修改,然后递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。 以下是回溯算法的一般步骤: 1. 定义空间:确定问题的空间,并定义一个数据结构来表示空间中的每个状态。 2. 确定约束条件:确定哪些状态是合法的,并定义一个函数来判断当前状态是否符合要求。 3. 确定搜索策略:确定搜索空间的顺序,并定义一个函数来生成下一个状态。 4. 搜索空间:使用递归函数搜索空间,如果当前状态符合要求,则将其加入到集中;否则,继续搜索下一个状态。 5. 回溯:当搜索完所有可能的状态后,回溯到上一个状态,继续搜索其他可能的状态。 以下是一个力扣题目的回溯算法 Java 实现示例: ``` class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { dfs(nums, 0); return res; } private void dfs(int[] nums, int start) { res.add(new ArrayList<>(path)); for (int i = start; i < nums.length; i++) { path.add(nums[i]); dfs(nums, i + 1); path.remove(path.size() - 1); } } } ``` 该算法用于求给定数组的所有子集。在递归函数中,我们首先将当前状态加入到集中,然后从当前位置开始搜索下一个状态。在搜索下一个状态时,我们将当前元素加入到路径中,并递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值