[LeetCode解題攻略] 36. Valid Sudoku

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

問題描述

給定一個 9x9 的數獨棋盤,請驗證這個棋盤是否有效。

數獨棋盤需滿足以下條件:

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

範例 2

輸入:
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"]]
輸出:false
解釋:第一列有兩個 8,因此這個棋盤是無效的。

解法一:使用集合檢查每一行、每一列和每個子方格

思路

我們可以將數獨棋盤視為三部分來驗證:

  1. 每一行是否有重複數字。
  2. 每一列是否有重複數字。
  3. 每一個 3x3 子方格是否有重複數字。

對於每部分,我們可以使用 集合 來檢查是否有重複數字:

  • 如果某數字已經存在於集合中,則表示有重複,棋盤無效。
  • 如果某位置是 '.',則忽略。

程式碼

class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
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):
num = board[i][j]
if num == '.':
continue

# 檢查行
if num in rows[i]:
return False
rows[i].add(num)

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

# 檢查子方格
box_index = (i // 3) * 3 + (j // 3)
if num in boxes[box_index]:
return False
boxes[box_index].add(num)

return True

時間與空間複雜度

  • 時間複雜度:O(1)
    數獨棋盤大小固定為 9x9,因此無論如何最多遍歷 81 個格子,時間複雜度為常數。
  • 空間複雜度:O(1)
    使用了三個長度為 9 的集合來存儲行、列和子方格的數字,總空間需求固定。

解法二:壓縮空間版本

思路

我們可以用 唯一標識符 來壓縮存儲需求,僅用一個集合來記錄每個數字是否出現過。

  1. 將每個數字出現的條件轉化為字串標識,例如:
    • 數字 5 出現在第 0 行,標識為 "5 in row 0"
    • 數字 5 出現在第 1 列,標識為 "5 in col 1"
    • 數字 5 出現在子方格 (0,0),標識為 "5 in box 0-0"
  2. 將這些標識存入集合,如果發現重複標識則返回 False

程式碼

class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
seen = set()
for i in range(9):
for j in range(9):
num = board[i][j]
if num == '.':
continue

row_key = f"{num} in row {i}"
col_key = f"{num} in col {j}"
box_key = f"{num} in box {i//3}-{j//3}"

if row_key in seen or col_key in seen or box_key in seen:
return False

seen.add(row_key)
seen.add(col_key)
seen.add(box_key)

return True

時間與空間複雜度

  • 時間複雜度:O(1)
    同樣固定遍歷 81 個格子。
  • 空間複雜度:O(1)
    由於最多存儲 81 條標識(每個數字最多三條標識),空間需求仍然是固定的。

解法三:逐步檢查行、列、子方格

思路

  1. 檢查每一行:將每一行的數字存入集合,檢查是否有重複。
  2. 檢查每一列:同樣將每一列的數字存入集合,檢查是否有重複。
  3. 檢查每個子方格:對每個子方格中的數字進行檢查。

這種方法較為直觀,分開處理每部分。

程式碼

class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
def isValidSet(values):
nums = [v for v in values if v != '.']
return len(nums) == len(set(nums))

# 檢查行
for row in board:
if not isValidSet(row):
return False

# 檢查列
for col in zip(*board):
if not isValidSet(col):
return False

# 檢查子方格
for i in range(0, 9, 3):
for j in range(0, 9, 3):
box = [board[x][y] for x in range(i, i + 3) for y in range(j, j + 3)]
if not isValidSet(box):
return False

return True

時間與空間複雜度

  • 時間複雜度:O(1)
    固定遍歷 81 個格子,且每次檢查集合操作為常數時間。
  • 空間複雜度:O(1)
    每次檢查時僅臨時存儲數字,無額外存儲需求。

結論

  1. 最佳解法解法二(壓縮空間版本),僅用一個集合,實現簡潔且高效。
  2. 其他解法:對於初學者,解法一解法三 更容易理解。
  3. 此題重點在於如何高效檢查每一行、每一列與每個子方格是否符合規範,並通過集合實現唯一性檢查。
歡迎來到我的部落格!這裡記錄了軟體工程師的日常生活點滴,並分享程式設計與演算法的實用教學。無論你是初學者還是有經驗的開發者,都能在這裡找到深入淺出的技術解析與實戰技巧。此外,我也會分享工作中的心路歷程與學習心得,讓你不僅學到技術,更能瞭解軟體開發的實際應用與挑戰。希望透過這個平台,能與你共同成長,激發對技術的熱情!
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
給定一個升序排序的整數數組 nums 和一個目標值 target,請找出目標值應插入的位置,保證插入後數組仍然是有序的。 必須設計一個演算法,其時間複雜度為 O(log n)。
給定一個按升序排序的整數數組 nums 和一個目標值 target,請在數組中找到目標值的 第一個 和 最後一個 位置,並以一個長度為 2 的列表返回結果。如果數組中不存在目標值,返回 [-1, -1]。
給定一個按照升序排序的整數數組,但該數組可能在某個未知的軸點旋轉(例如,nums = [4,5,6,7,0,1,2])。請你在數組中搜索目標值 target,如果找到則返回其索引,否則返回 -1。
給定一個只包含 '(' 和 ')' 的字串,找出其中包含的最長的有效括號子字串的長度。
實現一個函數來重新排列數字序列,使其成為下一個更大的排列。如果不存在更大的排列,則將其排列為最小的順序(即升序)。 這個排列必須是 "原地" 完成,也就是說,只能使用常數級額外空間。
給定一個字符串 s 和一個由多個單詞組成的列表 words,請找出所有能夠在字符串 s 中連續拼接所有單詞的子字符串的起始索引。
給定一個升序排序的整數數組 nums 和一個目標值 target,請找出目標值應插入的位置,保證插入後數組仍然是有序的。 必須設計一個演算法,其時間複雜度為 O(log n)。
給定一個按升序排序的整數數組 nums 和一個目標值 target,請在數組中找到目標值的 第一個 和 最後一個 位置,並以一個長度為 2 的列表返回結果。如果數組中不存在目標值,返回 [-1, -1]。
給定一個按照升序排序的整數數組,但該數組可能在某個未知的軸點旋轉(例如,nums = [4,5,6,7,0,1,2])。請你在數組中搜索目標值 target,如果找到則返回其索引,否則返回 -1。
給定一個只包含 '(' 和 ')' 的字串,找出其中包含的最長的有效括號子字串的長度。
實現一個函數來重新排列數字序列,使其成為下一個更大的排列。如果不存在更大的排列,則將其排列為最小的順序(即升序)。 這個排列必須是 "原地" 完成,也就是說,只能使用常數級額外空間。
給定一個字符串 s 和一個由多個單詞組成的列表 words,請找出所有能夠在字符串 s 中連續拼接所有單詞的子字符串的起始索引。
你可能也想看
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
給定一個整數陣列hand代表手牌點數,和參數groupSize。請問能不能每groupSize牌一組,每一組都拼出順子? 如果可以,返回True。如果無解,返回False。演算法使用最小堆積或排序。關鍵知識點:從小到大掃描每張牌,檢查能不能組成牌組長度為groupSize的順子即可。
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
子集合生成是一道經典的組合類上機考和面試題目。本篇文章介紹多個不同的解決方案,以及相關演算法框架。主要目標是給定n個相異的元素,產生所有的子集合。
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定我們一個整數陣列nums,要求我們把裡面的0做元素交換,把0都搬到陣列的右邊。題目要求必須in-place在原本的陣列裡面做操作,不可以額外建立新的陣列。 題目的原文敘述 測試範例 Example 1: Input: nums = [0,1,0,3,12] Outpu
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
給定一個整數陣列hand代表手牌點數,和參數groupSize。請問能不能每groupSize牌一組,每一組都拼出順子? 如果可以,返回True。如果無解,返回False。演算法使用最小堆積或排序。關鍵知識點:從小到大掃描每張牌,檢查能不能組成牌組長度為groupSize的順子即可。
Thumbnail
題目敘述 Single Number III 給定一個輸入陣列,已知有兩個烙單的數字,其他剩餘的數字都恰巧出現兩次。 請找出這兩個烙單的數字。 題目額外提出限制,請使用O(n)線性時間、O(1)常數空間複雜度的演算法。 測試範例 Example 1: Input: nums = [1,
Thumbnail
Leetcode 精選75題 題目與題解 熱門考點 目錄 (持續更新中) 建議從左側目錄 或者 按Ctrl+F輸入關鍵字進行搜尋
Thumbnail
子集合生成是一道經典的組合類上機考和面試題目。本篇文章介紹多個不同的解決方案,以及相關演算法框架。主要目標是給定n個相異的元素,產生所有的子集合。
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定我們一個整數陣列nums,要求我們把裡面的0做元素交換,把0都搬到陣列的右邊。題目要求必須in-place在原本的陣列裡面做操作,不可以額外建立新的陣列。 題目的原文敘述 測試範例 Example 1: Input: nums = [0,1,0,3,12] Outpu
Thumbnail
題目敘述 題目會給我們一個參數k 和 目標值n。 請問我們從1~9內挑k個相異的數字,使得他們的總和為n 的組合數有多少? 挑選時,每個數字必須相異,而且每個數字只能選一次。 題目的原文敘述 測試範例 Example 1: Input: k = 3, n = 7 Output: [