[LeetCode解題攻略] 37. Sudoku Solver

更新於 發佈於 閱讀時間約 12 分鐘

問題描述

編寫一個程式,求解數獨(Sudoku)問題。

數獨需要根據以下規則完成:

  1. 每一行只能包含數字 1-9,且不能重複。
  2. 每一列只能包含數字 1-9,且不能重複。
  3. 每一個 3x3 的子方格只能包含數字 1-9,且不能重複。

注意:

  • 數獨棋盤中的空格由 '.' 表示。
  • 必須就地修改數獨棋盤。

範例 1

raw-image
輸入:
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"]]
raw-image

解法一:回溯法

思路

回溯法(Backtracking),這是一種暴力解法。具體步驟如下:

  1. 從棋盤的左上角開始,找到第一個空格(.)。
  2. 嘗試將數字 1-9 填入這個位置,並檢查是否符合數獨規則。
  3. 如果符合,繼續檢查下一個空格;如果不符合,回退並嘗試下一個數字。
  4. 如果所有空格都被成功填充,則找到解。
  5. 如果嘗試所有數字後都無法填充當前空格,則回溯到上一個空格,並嘗試不同的數字。

程式碼

class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""

def is_valid(board, row, col, num):
num = str(num)
# 檢查行
for i in range(9):
if board[row][i] == num:
return False
# 檢查列
for i in range(9):
if board[i][col] == num:
return False
# 檢查子方格
box_row_start = (row // 3) * 3
box_col_start = (col // 3) * 3
for i in range(box_row_start, box_row_start + 3):
for j in range(box_col_start, box_col_start + 3):
if board[i][j] == num:
return False
return True

def backtrack(board):
for i in range(9):
for j in range(9):
if board[i][j] == '.':
for num in range(1, 10):
if is_valid(board, i, j, num):
board[i][j] = str(num)
if backtrack(board):
return True
# 回溯
board[i][j] = '.'
return False
return True

backtrack(board)

解釋

  1. is_valid 函數
    • 驗證某個數字是否可以填入指定位置。
    • 檢查行、列、以及子方格。
  2. backtrack 函數
    • 遍歷整個棋盤,找到第一個空格。
    • 嘗試填入 1-9,並遞歸解決。
    • 如果無法填入任何數字,回退到上一個空格並嘗試其他數字。
  3. 回溯步驟
    • 填入數字 → 檢查是否有效 → 遞歸到下一步。
    • 如果當前步驟無法進一步完成,將數字清除並嘗試下一個數字。

時間與空間複雜度

  • 時間複雜度:O(9^(n)),其中 n 是空格數量。
    • 每個空格最多嘗試 9 種數字,遞歸深度為空格數。
  • 空間複雜度:O(n),遞歸深度等於空格數。

解法二:優化回溯法

思路

我們可以通過額外的資料結構來加速檢查行、列和子方格是否有重複數字:

  1. 使用 3 個集合列表分別存儲每一行、每一列、以及每個子方格的數字。
  2. 當我們嘗試填入某個數字時,快速檢查集合是否包含該數字。

程式碼

class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""

rows = [set() for _ in range(9)]
cols = [set() for _ in range(9)]
boxes = [set() for _ in range(9)]

# 初始化集合
for i in range(9):
for j in range(9):
if board[i][j] != '.':
num = board[i][j]
rows[i].add(num)
cols[j].add(num)
boxes[(i // 3) * 3 + j // 3].add(num)

def backtrack(board):
for i in range(9):
for j in range(9):
if board[i][j] == '.':
for num in map(str, range(1, 10)):
box_index = (i // 3) * 3 + j // 3
if num not in rows[i] and num not in cols[j] and num not in boxes[box_index]:
# 填入數字
board[i][j] = num
rows[i].add(num)
cols[j].add(num)
boxes[box_index].add(num)

if backtrack(board):
return True

# 回溯
board[i][j] = '.'
rows[i].remove(num)
cols[j].remove(num)
boxes[box_index].remove(num)
return False
return True

backtrack(board)

解釋

  1. 優化檢查
    • 將每行、每列、每個子方格的數字存入集合。
    • 插入和檢查操作的時間複雜度為 O(1)。
  2. 回溯過程
    • 與解法一相同,但加入集合操作進行檢查,提升效率。

時間與空間複雜度

  • 時間複雜度:O(9^(n)),其中 n 是空格數量。
    • 優化的檢查操作不改變回溯的總體複雜度。
  • 空間複雜度:O(1)。
    • 集合大小固定為棋盤大小。

結論

  • 解法二通過使用集合加速檢查操作,使程式更高效且易於維護。
  • 整體回溯法對於數獨問題表現優秀,適用於求解這類組合問題。
歡迎來到我的部落格!這裡記錄了軟體工程師的日常生活點滴,並分享程式設計與演算法的實用教學。無論你是初學者還是有經驗的開發者,都能在這裡找到深入淺出的技術解析與實戰技巧。此外,我也會分享工作中的心路歷程與學習心得,讓你不僅學到技術,更能瞭解軟體開發的實際應用與挑戰。希望透過這個平台,能與你共同成長,激發對技術的熱情!
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
給定一個 9x9 的數獨棋盤,請驗證這個棋盤是否有效。
給定一個升序排序的整數數組 nums 和一個目標值 target,請找出目標值應插入的位置,保證插入後數組仍然是有序的。 必須設計一個演算法,其時間複雜度為 O(log n)。
給定一個按升序排序的整數數組 nums 和一個目標值 target,請在數組中找到目標值的 第一個 和 最後一個 位置,並以一個長度為 2 的列表返回結果。如果數組中不存在目標值,返回 [-1, -1]。
給定一個按照升序排序的整數數組,但該數組可能在某個未知的軸點旋轉(例如,nums = [4,5,6,7,0,1,2])。請你在數組中搜索目標值 target,如果找到則返回其索引,否則返回 -1。
給定一個只包含 '(' 和 ')' 的字串,找出其中包含的最長的有效括號子字串的長度。
實現一個函數來重新排列數字序列,使其成為下一個更大的排列。如果不存在更大的排列,則將其排列為最小的順序(即升序)。 這個排列必須是 "原地" 完成,也就是說,只能使用常數級額外空間。
給定一個 9x9 的數獨棋盤,請驗證這個棋盤是否有效。
給定一個升序排序的整數數組 nums 和一個目標值 target,請找出目標值應插入的位置,保證插入後數組仍然是有序的。 必須設計一個演算法,其時間複雜度為 O(log n)。
給定一個按升序排序的整數數組 nums 和一個目標值 target,請在數組中找到目標值的 第一個 和 最後一個 位置,並以一個長度為 2 的列表返回結果。如果數組中不存在目標值,返回 [-1, -1]。
給定一個按照升序排序的整數數組,但該數組可能在某個未知的軸點旋轉(例如,nums = [4,5,6,7,0,1,2])。請你在數組中搜索目標值 target,如果找到則返回其索引,否則返回 -1。
給定一個只包含 '(' 和 ')' 的字串,找出其中包含的最長的有效括號子字串的長度。
實現一個函數來重新排列數字序列,使其成為下一個更大的排列。如果不存在更大的排列,則將其排列為最小的順序(即升序)。 這個排列必須是 "原地" 完成,也就是說,只能使用常數級額外空間。
你可能也想看
Google News 追蹤
提問的內容越是清晰,強者、聰明人越能在短時間內做判斷、給出精準的建議,他們會對你產生「好印象」,認定你是「積極」的人,有機會、好人脈會不自覺地想引薦給你
Thumbnail
題目敘述 Combination Sum II_Leetcode #40 給定一個整數陣列candidates 和 目標值target。 每個陣列元素只能選擇一次,請問有多少種組合方法,可以使得組合數總和 = target? 請把滿足組合數總和 = target的組合方法以陣列的形式返回答案
Thumbnail
分享一個猜數字的遊戲題目,給予提示讓玩家找出正確的四位數密碼。
Thumbnail
給定一個9x9的輸入陣列代表數獨題目已經 部分作答 的狀態, 請驗證已經作答的部分是否為合法的Sudoku的輸入。 註: 合法的Sudoku輸入必須滿足這些規則 1~9每一直排恰好出現一次。 1~9每一橫排恰好出現一次。 1~9在3x3的小方陣裏恰好出現一次。
Thumbnail
LeetCode 是一個程式語言版的線上題庫平臺,提供題目描述、程式碼區塊、解題者分享的解法和疑問討論。藉由這篇文章分享我在 LeetCode 上的使用經驗和觀點,包括刷題的重要性、解題心態和練習目標。
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給我們一個字串s。 要求我們移除字串中的星號,還有刪除星號左手邊最靠近的第一個字元。 以字串的形式返回輸出答案。 題目的原文敘述 測試範例 Example 1: Input: s = "leet**cod*e" Output: "lecoe" Explanation:
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給我們一個參數k 和 目標值n。 請問我們從1~9內挑k個相異的數字,使得他們的總和為n 的組合數有多少? 挑選時,每個數字必須相異,而且每個數字只能選一次。 題目的原文敘述 測試範例 Example 1: Input: k = 3, n = 7 Output: [
提問的內容越是清晰,強者、聰明人越能在短時間內做判斷、給出精準的建議,他們會對你產生「好印象」,認定你是「積極」的人,有機會、好人脈會不自覺地想引薦給你
Thumbnail
題目敘述 Combination Sum II_Leetcode #40 給定一個整數陣列candidates 和 目標值target。 每個陣列元素只能選擇一次,請問有多少種組合方法,可以使得組合數總和 = target? 請把滿足組合數總和 = target的組合方法以陣列的形式返回答案
Thumbnail
分享一個猜數字的遊戲題目,給予提示讓玩家找出正確的四位數密碼。
Thumbnail
給定一個9x9的輸入陣列代表數獨題目已經 部分作答 的狀態, 請驗證已經作答的部分是否為合法的Sudoku的輸入。 註: 合法的Sudoku輸入必須滿足這些規則 1~9每一直排恰好出現一次。 1~9每一橫排恰好出現一次。 1~9在3x3的小方陣裏恰好出現一次。
Thumbnail
LeetCode 是一個程式語言版的線上題庫平臺,提供題目描述、程式碼區塊、解題者分享的解法和疑問討論。藉由這篇文章分享我在 LeetCode 上的使用經驗和觀點,包括刷題的重要性、解題心態和練習目標。
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給我們一個字串s。 要求我們移除字串中的星號,還有刪除星號左手邊最靠近的第一個字元。 以字串的形式返回輸出答案。 題目的原文敘述 測試範例 Example 1: Input: s = "leet**cod*e" Output: "lecoe" Explanation:
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給我們一個參數k 和 目標值n。 請問我們從1~9內挑k個相異的數字,使得他們的總和為n 的組合數有多少? 挑選時,每個數字必須相異,而且每個數字只能選一次。 題目的原文敘述 測試範例 Example 1: Input: k = 3, n = 7 Output: [