题目
判断一个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面试宝典》完整源码的大佬们,可订阅专栏后,搜索微信公众号“希望睿智”私信获取。