LeetCode 第37题:解数独

LeetCode 第37题:解数独

题目描述

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

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

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

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

难度

困难

题目链接

点击在LeetCode中查看题目

示例

示例 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. 使用回溯法尝试每个可能的数字
  2. 优化验证过程,避免重复检查
  3. 使用位运算加速判断

具体步骤:

  1. 预处理阶段:
    • 记录每行、每列、每个3x3方格中已有的数字
    • 找到所有需要填充的位置
  2. 回溯过程:
    • 对每个空位尝试1-9的数字
    • 验证当前数字是否有效
    • 继续填充下一个位置或回溯

时间复杂度:O(9^m),m为空格数量
空间复杂度:O(m),递归栈的深度

图解思路

回溯过程分析表

步骤 当前位置 尝试数字 结果 说明
初始状态 (0,2) 1-9 4可用 第一个空格位置
继续填充 (0,3) 1-9 6可用 成功填入4后继续
回溯 (2,0) 1-9 1可用 前面填充正确,继续
完成 (8,8) - 成功 所有空格已填满

状态记录表

数据结构 用途 实现方式 优势
行状态 记录每行数字 位运算 快速查找和更新
列状态 记录每列数字 位运算 空间效率高
宫状态 记录3x3宫数字 位运算 验证速度快

位运算示例

操作 二进制表示 说明
添加数字5 00010000 第5位置1
删除数字5 11101111 第5位置0
检查数字5 &00010000 与运算判断

代码实现

public class Solution {
    private int[] rows = new int[9];
    private int[] cols = new int[9];
    private int[] boxes = new int[9];
    private bool solved = false;
  
    public void SolveSudoku(char[][] board) {
        // 初始化状态
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int num = board[i][j] - '0';
                    int pos = 1 << (num - 1);
                    rows[i] |= pos;
                    cols[j] |= pos;
                    boxes[(i/3)*3 + j/3] |= pos;
                }
            }
        }
  
        // 开始回溯
        Backtrack(board, 0, 0);
    }
  
    private bool Backtrack(char[][] board, int row, int col) {
        if (col == 9) {
            row++;
            col = 0;
        }
        if (row == 9) return true;
  
        if (board[row][col] != '.') {
            return Backtrack(board, row, col + 1);
        }
  
        int boxIndex = (row/3)*3 + col/3;
        for (int num = 1; num <= 9; num++) {
            int pos = 1 << (num - 1);
            if ((rows[row] & pos) == 0 && 
                (cols[col] & pos) == 0 && 
                (boxes[boxIndex] & pos) == 0) {
      
                board[row][col] = (char)(num + '0');
                rows[row] |= pos;
                cols[col] |= pos;
                boxes[boxIndex] |= pos;
      
                if (Backtrack(board, row, col + 1)) {
                    return true;
                }
      
                board[row][col] = '.';
                rows[row] &= ~pos;
                cols[col] &= ~pos;
                boxes[boxIndex] &= ~pos;
            }
        }
  
        return false;
    }
}

执行结果

  • 执行用时:92 ms
  • 内存消耗:38.2 MB

代码亮点

  1. 🎯 使用位运算优化数字判断
  2. 💡 预处理初始状态,避免重复计算
  3. 🔍 高效的回溯策略
  4. 🎨 清晰的代码结构和命名

常见错误分析

  1. 🚫 回溯条件判断错误
  2. 🚫 状态重置不完整
  3. 🚫 位运算操作失误
  4. 🚫 边界条件处理不当

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
暴力回溯 O(9^m) O(m) 实现简单 效率低
位运算优化 O(9^m) O(1) 性能好 代码复杂
Dancing Links O(9^m) O(n^2) 最优解法 实现难度大

相关题目

posted @   旧厂街小江  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示