Python面试宝典第40题:有效的数独

题目

        判断一个9 x 9的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

        1、数字1-9在每一行只能出现一次。

        2、数字1-9在每一列只能出现一次。

        3、数字1-9在每一个以粗实线分隔的3 x 3宫内只能出现一次。

        下图是一个部分填充的有效的数独,数独部分空格内已填入了数字,空白格用字符'.'表示。

        备注:

        1、一个有效的数独(部分已被填充)不一定是可解的。

        2、只需要根据以上规则,验证已经填入的数字是否有效即可。

        3、给定数独序列只包含数字1-9和字符'.'。

        4、给定数独永远是9 x 9形式的。

        示例 1:

输入:
[
  ["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

        示例 2:

输入:
[
  ["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"]
]
输出: false
解释: 由于位于左上角的3 x 3宫内有两个8存在, 因此这个数独是无效的。

暴力法

        这道题主要考察应聘者对多维数组的理解和操作能力,对数据结构的应用能力,以及对数独规则的空间认知能力。求解本题最直观的方法是使用暴力法,暴力法求解数独的有效性意味着对每一行、每一列和每一个3 x 3小九宫格,都进行遍历,并检查是否包含重复数字。使用暴力法求解本题的主要步骤如下。

        1、遍历数独的每一行,检查每行中的数字1-9是否恰好出现一次。

        2、遍历数独的每一列,检查每列中的数字1-9是否恰好出现一次。

        3、遍历数独的每个3 x 3宫格,检查每个宫格中的数字1-9是否恰好出现一次。

        4、如果都恰好出现一次,则返回True,表示数独有效。否则返回False,表示数独无效

        根据上面的算法步骤,我们可以得出下面的示例代码。

def is_valid_sudoku_by_brute_force(board):
    # 遍历数独的每个单元格
    for i in range(9):
        for j in range(9):
            value = board[i][j]
            if value != '.':
                num = int(value)

                # 检查行
                if any(board[i][k] == str(num) for k in range(9) if k != j):
                    return False

                # 检查列
                if any(board[k][j] == str(num) for k in range(9) if k != i):
                    return False

                # 检查3 x 3宫格
                start_i, start_j = (i // 3) * 3, (j // 3) * 3
                if any(board[k][l] == str(num) for k in range(start_i, start_i + 3) 
                       for l in range(start_j, start_j + 3) if (k, l) != (i, j)):
                    return False

    # 所有检查都通过,数独有效
    return True

sudoku_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"]
]
print(is_valid_sudoku_by_brute_force(sudoku_board))

sudoku_board = [
  ["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"]
]
print(is_valid_sudoku_by_brute_force(sudoku_board))

哈希法

        为了减少暴力法不必要的遍历,我们可以使用哈希法仅遍历数独一次,并同时检查行、列和3 x 3小九宫格的合法性。使用哈希法求解本题的主要步骤如下。

        1、初始化哈希表。

        (1)rows:一个包含9个空集合的列表,用于跟踪每行中出现过的数字。

        (2)cols:一个包含9个空集合的列表,用于跟踪每列中出现过的数字。

        (3)boxes:一个包含3行3列的嵌套列表,每个元素都是一个空集合,用于跟踪每个3 x 3宫格中出现过的数字。

        2、遍历数独。

        (1)使用双重循环遍历数独的每个单元格。

        (2)对于每个非空单元格,检查该数字是否已经存在于对应的行、列或3 x 3宫格中。如果存在,则返回False,表示数独无效。否则,将该数字添加到对应的行、列或3 x 3宫格的集合中。

        3、返回结果。如果遍历完整个数独后,没有发现重复数字,则返回True,表示数独有效。

        根据上面的算法步骤,我们可以得出下面的示例代码。

def is_valid_sudoku_by_hash(board):
    # 初始化哈希表,以跟踪每行、每列和每个3 x 3宫格中出现过的数字
    rows = [set() for _ in range(9)]
    cols = [set() for _ in range(9)]
    boxes = [[set() for _ in range(3)] for _ in range(3)]

    # 遍历数独的每个单元格
    for i in range(9):
        for j in range(9):
            value = board[i][j]
            if value != '.':
                # 检查行
                if value in rows[i]:
                    return False
                rows[i].add(value)

                # 检查列
                if value in cols[j]:
                    return False
                cols[j].add(value)

                # 检查3 x 3宫格
                box_index = (i // 3, j // 3)
                if value in boxes[box_index[0]][box_index[1]]:
                    return False
                boxes[box_index[0]][box_index[1]].add(value)

    # 所有检查都通过,数独有效
    return True

sudoku_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"]
]
print(is_valid_sudoku_by_hash(sudoku_board))

sudoku_board = [
  ["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"]
]
print(is_valid_sudoku_by_hash(sudoku_board))

位运算法

        位运算法是一种节省内存的方法,因为它使用位操作而不是哈希表来跟踪数字的出现情况。位运算法的基本思想是:利用32位整数的每一位来代表一个数字是否出现过。对于数独问题,我们只需要前9位,每一位代表1到9中的一个数字;如果某一位被设置,则表示对应的数字已经被使用。使用位运算法求解本题的主要步骤如下。

        1、初始化位向量。为每一行、每一列和每个3 x 3宫格创建一个32位的整数变量作为位向量。

        2、遍历数独。遍历数独中的每个元素,对于每个非空元素,根据该元素的值更新对应行、列和3 x 3宫格的位向量。

        3、检查位向量。确保在任何一行、任何一列或任何一个3x3宫格中,对应的位向量不会因为设置了相同的比特位而导致冲突。

def is_valid_sudoku_by_bitwise(board):
    # 初始化用于跟踪每行、每列和每个3 x 3宫格的位向量
    rows = [0] * 9
    cols = [0] * 9
    boxes = [[0 for _ in range(3)] for _ in range(3)]

    # 遍历数独的每个单元格
    for i in range(9):
        for j in range(9):
            value = board[i][j]
            if value != '.':
                # 将数字转换为0-8的索引
                num = int(value) - 1
                # 计算对应的比特位
                bit = 1 << num

                # 检查行
                if rows[i] & bit:
                    return False
                rows[i] |= bit

                # 检查列
                if cols[j] & bit:
                    return False
                cols[j] |= bit

                # 检查3 x 3宫格
                box_index = (i // 3, j // 3)
                if boxes[box_index[0]][box_index[1]] & bit:
                    return False
                boxes[box_index[0]][box_index[1]] |= bit

    # 所有检查都通过,数独有效
    return True

sudoku_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"]
]
print(is_valid_sudoku_by_bitwise(sudoku_board))

sudoku_board = [
  ["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"]
]
print(is_valid_sudoku_by_bitwise(sudoku_board))

总结

        使用暴力法求解本题时,对于数独中的每个单元格,我们都需要检查其所在的行、列和3 x 3宫格中是否有重复的数字。因此,时间复杂度为O(n^3),其中n = 9。其空间复杂度为O(1),不需要额外的数据结构来记录状态。

        哈希法和位运算法的时间复杂度均为O(n^2),这是因为都需要遍历整个数独。两者的空间复杂度均为O(n),但位运算法实际上只使用了9位来存储数字1-9的出现情况,故其实际占用的空间远小于哈希法。总的来说,哈希法提供了较快的查找速度和较为简单的实现,但空间占用略大于位运算法。位运算法具有更高的空间效率和紧凑的代码,但可能不如哈希法那样易于理解。

💡 需要《Python面试宝典》完整源码的大佬们,可订阅专栏后,搜索微信公众号“希望睿智”私信获取。

  • 42
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

希望_睿智

您的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值