算法题 有效的数独

LeetCode 36. 有效的数独

问题描述

判断一个 9x9 的数独是否有效。需要满足以下规则:

  1. 数字 1-9 在每一行只能出现一次
  2. 数字 1-9 在每一列只能出现一次
  3. 数字 1-9 在每一个 3x3 宫格内只能出现一次

注意

  • 空白格用 '.' 表示
  • 只需验证已填入数字,不要求数独可解
  • 部分填充的数独可能有效

示例
在这里插入图片描述

输入: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"]]
输出:true

算法思路

哈希表/数组标记法

  1. 数据结构
    • 创建三个二维数组(或哈希表):
      • rows[9][9]:记录每行数字出现情况
      • cols[9][9]:记录每列数字出现情况
      • boxes[9][9]:记录每个宫格数字出现情况
  2. 宫格索引计算
    • 对于位置 (i, j),宫格索引 = (i/3)*3 + j/3
  3. 遍历数独
    • 遍历每个单元格:
      • 遇到数字时,计算其在数组中的索引(num = char - '1'
      • 检查该数字在对应行、列、宫格中是否已存在
      • 若存在则返回无效,否则标记为存在
  4. 返回结果:遍历完成后返回有效

代码实现

class Solution {
    public boolean isValidSudoku(char[][] board) {
        // 初始化记录数组
        boolean[][] rows = new boolean[9][9];   // 记录每行数字出现情况
        boolean[][] cols = new boolean[9][9];   // 记录每列数字出现情况
        boolean[][] boxes = new boolean[9][9];  // 记录每个宫格数字出现情况
        
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                char c = board[i][j];
                // 跳过空白格
                if (c == '.') continue;
                
                // 将字符转换为数字索引(0-8)
                int num = c - '1';  // '1'->0, '2'->1, ..., '9'->8
                
                // 计算宫格索引(0-8)
                int boxIndex = (i / 3) * 3 + j / 3;
                
                // 检查数字是否已存在
                if (rows[i][num] || cols[j][num] || boxes[boxIndex][num]) {
                    return false;
                }
                
                // 标记数字已出现
                rows[i][num] = true;
                cols[j][num] = true;
                boxes[boxIndex][num] = true;
            }
        }
        return true;
    }
}

算法分析

  • 时间复杂度:O(1)
    • 固定遍历 81 个单元格(9x9)
  • 空间复杂度:O(1)
    • 使用固定大小的数组(3x9x9=243 个布尔值)

算法过程

示例输入:

  1. 遍历位置 (0,0)'5'
    • num = '5'-'1' = 4
    • boxIndex = (0/3)*3 + 0/3 = 0
    • 标记:rows[0][4]=true, cols[0][4]=true, boxes[0][4]=true
  2. 遍历位置 (0,1)'3'
    • num = 2
    • boxIndex = 0
    • 标记:rows[0][2]=true, cols[1][2]=true, boxes[0][2]=true
  3. 遍历位置 (1,0)'6'
    • num = 5
    • boxIndex = (1/3)*3 + 0/3 = 0
    • 标记:rows[1][5]=true, cols[0][5]=true, boxes[0][5]=true
  4. 继续遍历:所有数字均无重复,返回 true

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准有效数独
    char[][] board1 = {
        {'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'}
    };
    System.out.println("Test 1: " + solution.isValidSudoku(board1)); // true
    
    // 测试用例2:行重复
    char[][] board2 = {
        {'8','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'}
    };
    System.out.println("Test 2: " + solution.isValidSudoku(board2)); // false
    
    // 测试用例3:列重复
    char[][] board3 = {
        {'5','3','.','.','7','.','.','.','.'},
        {'6','.','.','1','9','5','.','.','.'},
        {'.','9','8','.','.','.','.','6','.'},
        {'5','.','.','.','6','.','.','.','3'},
        {'4','.','.','8','.','3','.','.','1'},
        {'7','.','.','.','2','.','.','.','6'},
        {'.','6','.','.','.','.','2','8','.'},
        {'.','.','.','4','1','9','.','.','5'},
        {'.','.','.','.','8','.','.','7','9'}
    };
    System.out.println("Test 3: " + solution.isValidSudoku(board3)); // false
    
    // 测试用例4:宫格重复
    char[][] board4 = {
        {'5','3','.','.','7','.','.','.','.'},
        {'6','5','.','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'}
    };
    System.out.println("Test 4: " + solution.isValidSudoku(board4)); // false
    
    // 测试用例5:空白数独
    char[][] board5 = {
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'}
    };
    System.out.println("Test 5: " + solution.isValidSudoku(board5)); // true
    
    // 测试用例6:单宫格重复
    char[][] board6 = {
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','1','.','.','.','.','.'},
        {'.','.','.','.','1','.','.','.','.'}, // 两个1在同一宫格
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'},
        {'.','.','.','.','.','.','.','.','.'}
    };
    System.out.println("Test 6: " + solution.isValidSudoku(board6)); // false
}

关键点

  1. 宫格索引计算

    • boxIndex = (i/3)*3 + j/3
    • 9x9 网格划分为 9 个 3x3 宫格
  2. 数字索引转换

    • num = char - '1''1'→0, '2'→1, …, '9'→8
  3. 高效检查

    • 使用布尔数组记录出现情况
    • 一次遍历完成所有检查
  4. 空白格处理

    • 遇到 '.' 直接跳过

常见问题

  1. 为什么宫格索引用 (i/3)*3 + j/3
    该公式将网格划分为9个宫格:

    • i/3 确定行方向宫格索引(0-2)
    • j/3 确定列方向宫格索引(0-2)
    • (i/3)*3 + j/3 将二维宫格索引映射为一维(0-8)
  2. 为什么用 char - '1' 而不用 char - '0'

    • char - '1''1' 映射为 0,'9' 映射为 8
    • 符合数组索引从 0 开始的特点
    • 若用 char - '0' 需要额外减1操作
  3. 空白格会影响判断吗?
    不会,代码中遇到 '.' 直接跳过,只检查数字。

  4. 能否用 HashSet 代替数组?
    可以,但数组更高效:

    Set<String> seen = new HashSet<>();
    for (...) {
        if (c != '.') {
            String rowKey = i + "r" + num;
            String colKey = j + "c" + num;
            String boxKey = boxIndex + "b" + num;
            if (!seen.add(rowKey) || !seen.add(colKey) || !seen.add(boxKey)) 
                return false;
        }
    }
    
  5. 最坏情况时间复杂度是多少?
    始终为 O(1),因为网格大小固定(9x9=81 个单元格)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值