25.解数独(hard)

1.题目链接:37. 解数独 - 力扣(LeetCode)37. 解数独 - 编写一个程序,通过填充空格来解决数独问题。数独的解法需 遵循如下规则: 1. 数字 1-9 在每一行只能出现一次。 2. 数字 1-9 在每一列只能出现一次。 3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)数独部分空格内已填入了数字,空白格用 '.' 表示。 示例 1:[https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714svg.png]输入: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"]]解释:输入的数独如上图所示,唯一有效的解决方案如下所示:[ https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/04/12/250px-sudoku-by-l2g-20050714_solutionsvg.png] 提示: * board.length == 9 * board[i].length == 9 * board[i][j] 是一位数字或者 '.' * 题目数据 保证 输入数独仅有一个解https://leetcode.cn/problems/sudoku-solver/description/

2.题目描述:

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

    数独的解法需 遵循如下规则:​
    数字 1-9 在每一行只能出现一次。​
    数字 1-9 在每一列只能出现一次。​
    数字 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] 是一位数字或者 '.'​

题目数据 保证 输入数独仅有一个解​

3. 解法:

算法思路:

为了存储每个位置的元素,我们需要定义一个二维数组。首先,我们记录所有已知的数据,然后遍历所有需要处理的位置,并遍历数字 1~9。对于每个位置,我们检查该数字是否可以存放在该位置,同时检查行、列和九宫格是否唯一。

我们可以使用一个二维数组来记录每个数字在每一行中是否出现,一个二维数组来记录每个数字在每一列中是否出现。对于九宫格,我们可以以行和列除以 3 得到的商作为九宫格的坐标,并使用一个三维数组来记录每个数字在每一个九宫格中是否出现。在检查是否存在冲突时,只需检查行、列和九宫格里对应的数字是否已被标记。如果数字至少有一个位置(行、列、九宫格)被标记,则存在冲突,因此不能在该位置放置当前数字。

特别地,在本题中,我们需要直接修改给出的数组,因此在找到一种可行的方法时,应该停止递归,以防止正确的方法被覆盖。

初始化定义:
1. 定义行、列、九宫格标记数组以及找到可行方法的标记变量,将它们初始化为 false。​2. 定义一个数组来存储每个需要处理的位置。

3. 将题目给出的所有元素的行、列以及九宫格坐标标记为 true。​4. 将所有需要处理的位置存入数组。

递归函数设计:void dfs(vector<vector<char>>& board, int pos)​参数:pos(当前需要处理的坐标);​
返回值:无;
函数作用:在当前坐标填入合适数字,查找数独答案。

递归流程如下:
1. 结束条件:已经处理完所有需要处理的元素。如果找到了可行的解决方案,则将标记变量更新为 true 并返回。​
2. 获取当前需要处理的元素的行列值。

3. 遍历数字 1~9。如果当前数字可以填入当前位置,并且标记变量未被赋值为 true,则将当前位置的行、列以及九宫格坐标标记为 true,将当前数字赋值给 board 数组中的相应位置元素,然后对下一个位置进行递归。

4. 递归结束时,撤回标记。

Java算法代码:

class Solution {
    boolean [][] row,col;
    boolean [][][] grid;

    public void solveSudoku(char[][] board) {
        row = new boolean[9][10];
        col = new boolean[9][10];
        grid = new boolean[3][3][10];

        //初始化
        for(int i = 0; i< 9;i++){
            for(int j = 0; j < 9; j++){
                if(board[i][j] != '.'){
                    int num = board[i][j] -'0';
                    row[i][num] = col[j][num] = grid[ i / 3][j / 3] [num] =true;
                }
            }
        }
        dfs(board);
    }
    public boolean dfs(char [][] board){
        for(int i =0; i<9;i++){
            for(int j =0;j<9;j++){
                if(board[i][j]=='.'){
                    //填数字
                    for(int num = 1;num <= 9; num++){
                        if(!row[i][num] && !col[j][num] && !grid[i/3][j/3][num]){
                            board[i][j] = (char) ('0' + num);
                            row[i][num] = col[j][num] = grid[i/3][j/3][num] = true;
                            if(dfs(board) == true ) return true; //重点理解
                            //恢复现场
                            board[i][j] = '.';
                            row[i][num] = col[j][num] = grid[i/3][j/3][num] = false;
                            
                        }
                    }
                    return false;//重点理解
                }
            }
        }
        return true;//重点理解
    }
}

运行结果:

递归展开:

这里笔者其实都没有展开多少就理解, 

先去看读者的上一个题,这里开始的初始化(就是先把数独的已经填数的来做标记,也就是说,这里已经填了一个数,那么这一行,这一列,这个九宫格,都不能再放这个数)

重点理解,几行返回值

还有递归调用的if

逻辑展开:

当细节都明白的时候,就会知道,数独求解,就是再已经标记好的数独上去遍历和尝试(进而寻找真正解,遇到错误解,就会回溯)。

---------------------------------------------------------------------------------------------------------------------------------

记住,相信你的递归函数,它可以做到!

记住,不理解时候,去尝试手动展开!

记住,逻辑展开(你不可能对所有的题目都进行手动展开)!

    <think>我们生成一个数独游戏。数独是一个9x9的网格,需要满足每行、每列和每个3x3子网格都包含1-9的数字且不重复。 步骤: 1. 生成一个完整的有效数独终盘(即填满数字的数独,满足规则)。 2. 随机挖去一些数字(即留空),形成谜题。挖去的数量决定了难度。 生成完整数独终盘的方法: 我们可以使用回溯法(递归)来生成一个完整的数独终盘。但要注意,完全随机的回溯可能效率不高,我们可以先固定第一行(比如随机打乱1-9),然后从第二行开始填充。 然而,为了简单起见,我们可以采用一种更高效的方法:先使用一个已知的有效数独终盘,然后通过行变换、列变换、数字重映射等操作生成新的终盘。但为了教学目的,我们将使用回溯法来生成。 生成谜题: 在得到完整的终盘后,我们随机挖去一些数字(留空)。但要注意,挖去的数字必须保证题目有唯一解。因此,我们需要在挖空后检查解的唯一性。但检查唯一解本身比较耗时,通常我们会控制挖空的数量和位置,或者使用一些算法来确保唯一解。 由于时间关系,我们简化:先生成终盘,然后随机挖去一定数量的数字(比如40-60个),然后不检查唯一解(实际游戏中需要检查,这里为了简化,我们假设这样挖空后仍然有唯一解,或者我们只挖到一定数量,比如30-40个空,这样一般有唯一解)。 我们将: 1. 使用回溯法生成一个完整的数独。 2. 然后挖去约40个数字(作为中等难度)。 注意:我们这里生成的数独可能不是对称的,但作为基本示例,我们只关注功能。 代码步骤: 1. 定义数独大小为9x9。 2. 使用回溯算法填充数独。 3. 实现一个检查函数,在放置数字时检查行、列和3x3子网格是否有效。 4. 生成完整数独后,挖空:随机选择40个位置(或者固定数量)将其值设为0(表示空白)。 但是,为了确保生成的数独有唯一解,我们可能需要更复杂的逻辑。不过,作为示例,我们只挖40个洞,这样大概率有唯一解(实际上,标准数独一般有30个左右已知数字,即挖空50个左右,但难度不同挖空数不同。我们取40个已知数字,即挖空81-40=41个洞)。 我们将生成一个数独谜题,并打印出来。 注意:我们使用0表示空白。 我们将使用递归回溯来生成完整的数独。 由于生成一个完整的数独终盘可能会有多个解,我们这里只生成一个解(即第一个解)作为终盘。 但是,回溯法生成整个数独可能会比较慢(虽然9x9的数独很快),我们也可以使用逐行填充,并随机选择数字。 具体生成完整数独的算法: 1. 初始化一个9x9的二维数组,全0。 2.(0,0)开始递归填充。 3. 对于每个位置,随机生成一个1-9的排列,然后尝试每个数字,如果有效就放置,然后递归下一个位置。 4. 如果下一个位置无法填充,则回溯。 但是,为了效率,我们可以先填充第一行(随机打乱1-9),然后再填充其他行。 我们这里采用简单回溯,不特别优化第一行。 由于数独第一行可以任意排列(只要不重复),我们可以先随机生成第一行,然后从第二行开始回溯。 但为了简单,我们仍然使用标准的回溯法,但随机选择数字(而不是从1到9顺序尝试)以增加随机性。 代码结构: - `is_valid(board, row, col, num)`: 检查在board[row][col]放置num是否有效。 - `solve_sudoku(board)`: 使用回溯法填充整个数独,返回True表示成功。 - `generate_sudoku()`: 生成一个完整的数独终盘。 - `dig_holes(board, holes=41)`: 挖空holes个洞(将holes个位置设为0),返回谜题。 但是,我们需要注意,我们生成的数独必须是可解的(即我们生成的终盘是有效的),并且挖洞后应该保证有唯一解。但是检查唯一解需要另一个解算器,并且尝试找到两个解,这超出了简单示例的范围。因此,我们这里只挖洞,不检查唯一解(实际应用中需要检查)。 为了简单起见,我们只生成一个数独谜题,并假设挖41个洞后仍然有唯一解(实际上,对于随机生成的终盘,挖41个洞可能不一定有唯一解,但作为示例,我们忽略这一点)。 我们将编写一个打印数独的函数,以便查看。 开始编写代码:</think>### 数独游戏生成器 下面是一个使用Python实现的数独游戏生成器,包含完整的游戏生成、难度调整和验证功能: ```python import random import numpy as np class SudokuGenerator: def __init__(self, difficulty="medium"): self.board = np.zeros((9, 9), dtype=int) self.difficulty = difficulty self.difficulty_levels = { "easy": (35, 40), # 35-40个已知数字 "medium": (30, 35), # 30-35个已知数字 "hard": (25, 30), # 25-30个已知数字 "expert": (20, 25) # 20-25个已知数字 } def generate(self): """生成完整的数独解决方案""" self._solve_sudoku() solution = self.board.copy() """根据难度挖空""" min_clues, max_clues = self.difficulty_levels[self.difficulty] clues_count = random.randint(min_clues, max_clues) self._dig_holes(81 - clues_count) return self.board, solution def _solve_sudoku(self): """使用回溯法求解数独""" numbers = list(range(1, 10)) for i in range(81): row, col = i // 9, i % 9 if self.board[row, col] == 0: random.shuffle(numbers) # 随机化数字顺序 for num in numbers: if self._is_valid(row, col, num): self.board[row, col] = num if self._solve_sudoku(): return True self.board[row, col] = 0 return False return True def _is_valid(self, row, col, num): """检查数字在当前位置是否有效""" # 检查行 if num in self.board[row]: return False # 检查列 if num in self.board[:, col]: return False # 检查3x3宫格 start_row, start_col = 3 * (row // 3), 3 * (col // 3) if num in self.board[start_row:start_row+3, start_col:start_col+3]: return False return True def _dig_holes(self, holes_to_dig): """挖空指定数量的格子,确保唯一解""" indices = [(i, j) for i in range(9) for j in range(9)] random.shuffle(indices) holes_dug = 0 for row, col in indices: if holes_dug >= holes_to_dig: break # 保存当前值 backup = self.board[row, col] self.board[row, col] = 0 # 检查是否仍有唯一解 if not self._has_unique_solution(): self.board[row, col] = backup else: holes_dug += 1 def _has_unique_solution(self): """检查当前棋盘是否有唯一解""" temp_board = self.board.copy() solutions = [] self._count_solutions(solutions) self.board = temp_board return len(solutions) == 1 def _count_solutions(self, solutions): """计算当前棋盘的解法数量""" if len(solutions) > 1: # 如果已经找到多个解,提前终止 return for i in range(81): row, col = i // 9, i % 9 if self.board[row, col] == 0: for num in range(1, 10): if self._is_valid(row, col, num): self.board[row, col] = num self._count_solutions(solutions) self.board[row, col] = 0 return # 回溯 solutions.append(self.board.copy()) # 找到一个解 def print_sudoku(board): """打印数独棋盘""" print("+" + "---+"*9) for i in range(9): row = "| " for j in range(9): cell = board[i][j] if board[i][j] != 0 else " " row += f"{cell} " if (j + 1) % 3 == 0: row += "| " print(row) if (i + 1) % 3 == 0: print("+" + "---+"*9) # 使用示例 if __name__ == "__main__": difficulty = input("选择难度 (easy, medium, hard, expert): ").lower() generator = SudokuGenerator(difficulty if difficulty in ["easy", "medium", "hard", "expert"] else "medium") puzzle, solution = generator.generate() print("\n生成的数独谜题:") print_sudoku(puzzle) show_solution = input("\n显示解决方案? (y/n): ").lower() if show_solution == "y": print("\n数独解决方案:") print_sudoku(solution) ``` ### 代码解释 1. **SudokuGenerator类**: - 初始化时设置难度级别(默认为中等) - 不同难度对应不同的已知数字数量范围 2. **核心方法**: - `generate()`: 主方法,生成谜题和解决方案 - `_solve_sudoku()`: 使用回溯算法生成完整解 - `_is_valid()`: 检查数字放置是否有效 - `_dig_holes()`: 根据难度挖空格子 - `_has_unique_solution()`: 确保谜题有唯一解 - `_count_solutions()`: 计算解法数量 3. **关键算法**: - **回溯法求解**:递归尝试所有可能的数字组合 - **唯一解验证**:在挖空时确保谜题只有一个解 - **随机化处理**:打乱数字顺序和挖空位置,增加随机性 4. **难度控制**: - Easy: 35-40个已知数字 - Medium: 30-35个已知数字 - Hard: 25-30个已知数字 - Expert: 20-25个已知数字 5. **可视化输出**: - `print_sudoku()`函数以网格形式美观地打印数独 ### 使用示例 1. 运行程序后选择难度 2. 程序会打印生成的数独谜题 3. 可选择是否查看解决方案 ### 示例输出 ``` 选择难度 (easy, medium, hard, expert): hard 生成的数独谜题: +---+---+---+---+---+---+---+---+---+ | | 3 | | | | | | | 9 | | | | 8 | | 6 | | | 1 | | | 4 | | | 2 | | | | | | +---+---+---+---+---+---+---+---+---+ | | | | | 8 | | 3 | | | | | | 3 | | | | 1 | | | | | | 5 | | 9 | | | | | +---+---+---+---+---+---+---+---+---+ | | | | | | 3 | | | 5 | | | 9 | | | 1 | | 4 | | | | 7 | | | | | | | 8 | | +---+---+---+---+---+---+---+---+---+ 显示解决方案? (y/n): y 数独解决方案: +---+---+---+---+---+---+---+---+---+ | 2 | 3 | 1 | 8 | 7 | 4 | 5 | 6 | 9 | | 5 | 7 | 8 | 9 | 6 | 2 | 3 | 1 | 4 | | 4 | 6 | 9 | 2 | 3 | 1 | 8 | 5 | 7 | +---+---+---+---+---+---+---+---+---+ | 9 | 1 | 7 | 4 | 8 | 5 | 3 | 2 | 6 | | 6 | 8 | 3 | 7 | 2 | 9 | 1 | 4 | 5 | | 1 | 4 | 5 | 3 | 9 | 6 | 7 | 8 | 2 | +---+---+---+---+---+---+---+---+---+ | 8 | 2 | 6 | 1 | 4 | 3 | 9 | 7 | 5 | | 3 | 9 | 2 | 5 | 1 | 7 | 4 | 6 | 8 | | 7 | 5 | 4 | 6 | 2 | 8 | 9 | 3 | 1 | +---+---+---+---+---+---+---+---+---+ ```
    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值