【Leetcode】python - [36] Valid Sudoku 個人解法筆記 (內含範例程式碼)

題目出處

36. Valid Sudoku

難度

Medium

題目分類

array, hash-table, matrix

個人範例程式碼

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:

        # Each row must contain the digits 1-9 without repetition.
        for each_row in board:
            if self.checknums(each_row):
                pass
            else:
                return False # early return

        # Each column must contain the digits 1-9 without repetition.
        transpose_board = list(zip(*board)) # unpack list, then rezip by column
        for each_row in transpose_board:
            if self.checknums(each_row):
                pass
            else:
                return False # early return   

        # Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition.
        for y_idx in range(0, 9, 3):
            for x_idx in range(0, 9, 3): 
                collection = self.getsubbox(board, x_idx, y_idx)
                if self.checknums(collection):
                    pass
                else:
                    return False # early return

        return True

    def getsubbox(self, board, start_x ,start_y):
        collection = []
        for y_idx in range(3):
            for x_idx in range(3):
                collection.append(board[start_y+y_idx][start_x+x_idx])
        return collection

    def checknums(self, nums: List[str]) -> bool:
        keep = set()
        for ele in nums:
            if ele == ".":
                pass
            elif ele in keep:
                return False
            else:
                keep.add(ele)

        return True

Time Complexity

x

算法說明

這題,基本上就是條件多了一點,
要比較有耐心的處理,並且把指定的項目都拿出來,
除此之外難度不高。

要有耐心處理條件反而是我覺得這題難的地方。

我的算法基本上就依照題目敘述,
先建立一個檢查的函數,之後我們都以「9個位置為單位」傳入,
檢查出結果並回傳。

比較麻煩的大概是小的 sub-box,這裡要有點耐心處理。

幾個小細節可以注意:

「轉置矩陣」好用技巧:用「*」拆開 list後,再用「zip」重組。

簡單來說,想快速得到轉置矩陣(像這題我們為了把 column 拉出來),
我們可以用「*」拆開 list後,再用「zip」重組。

a = [[1,2],[3,4]]
print(*a)  #  [1,2],[3,4] <- 拆開 list
print(list(zip(*a))) # [[1,3],[2,4]]  <- 拆開 list,zip 重組,從 zip 轉 list 格式

詳情可以看我的另外一篇:

early return:只要發現不合規則,即可以 return。

相對於此方法的就是建立一個 flag,過程中全檢查,最後才回傳這個 flag,但因為我們早知道結果,不需要全部組合都檢查才 return。

搜尋請用 set/ dict 而不要用 list:set/dict 的查找速度為 O(1),list 為 O(n)。

簡單來說,只要單純是做搜尋功能的,建議都用 set/dict,查找速度為 O(1),
有些人 list 因為太方便用習慣了,但 list 查詢速度為 O(n),可能會浪費很多時間在查找上。

一提再提的重點:只要有「找重複」的事情,幾乎必使用 set()

這題換句話說就是「找有沒有重複數字」,這部分應該會使用到 set。

set 在「找尋重複項目」可以發揮最快的時間,幾乎是「找重複問題」必用。

corner case 特殊情況處理

x

Boundary conditions/ Edge conditions 邊際情況處理

x

討論區看到的一些酷解法 - 1

這個作者很聰明的,把在 sudoku 出現的每一個元素都弄成一個 set,
依據規則總共有三種:

  • Each row must contain the digits 1-9 without repetition. 單一元素建立 set (出現過的數字, y)
  • Each column must contain the digits 1-9 without repetition. 單一元素建立 set (x, 出現過的數字)
  • Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition. 單一元素建立 set (x//3, y//3, 出現過的數字)
  • 這個解法漂亮的地方在於,[0,1,2,3,4,5,6,7,8] 經過 // 3 後,會變成 [0,0,0,1,1,1,2,2,2],
    因此我們就可以比較重複元素了!

    可以參考:

    範例程式碼

    看完解答後我自己也照他的概念重寫了自己的,真的快又準確,好猛…

    class Solution:
        def isValidSudoku(self, board: List[List[str]]) -> bool:
    
            # Each row must contain the digits 1-9 without repetition.
            rule1_set = set()
            # Each column must contain the digits 1-9 without repetition.
            rule2_set = set()    
            # Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition.
            rule3_set = set()
    
            for y, each_row in enumerate(board):
                for x, ele in enumerate(each_row):
                    if ele != '.': 
                        rule1_ele = (ele, y)
                        rule2_ele = (x, ele)
                        rule3_ele = (x//3, y//3, ele)
    
                        if rule1_ele in rule1_set:
                            return False
                        else:
                            rule1_set.add(rule1_ele)
    
                        if rule2_ele in rule2_set:
                            return False
                        else:
                            rule2_set.add(rule2_ele)
    
                        if rule3_ele in rule3_set:
                            return False
                        else:
                            rule3_set.add(rule3_ele)
    
            return True
    

    corner case 特殊情況處理

    注意要處理 “.”,不然會以為已經出現過了。

    Boundary conditions/ Edge conditions 邊際情況處理

    x

    討論區看到的一些酷解法 - 2

    與上面酷解法差不多,這邊只是想強調比對時,可以用

    if len(list) == len(set(list)):
        # 兩者相等,表示 set 未刪去東西 (無重複項)
    else:
        # 兩者不相等,表示 set 有刪去東西 (有重複項)
    

    注意:不可以比較「 list = set(list))」,因為「資料型態不同」,必定為 False

    可以參考:

    範例程式碼

    class Solution:
        def isValidSudoku(self, board: List[List[str]]) -> bool:
    
            # Each row must contain the digits 1-9 without repetition.
            rule1_list = []
            # Each column must contain the digits 1-9 without repetition.
            rule2_list = []   
            # Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition.
            rule3_list = []
    
            for y, each_row in enumerate(board):
                for x, ele in enumerate(each_row):
                    if ele != '.':
                        rule1_ele = (ele, y)
                        rule2_ele = (x, ele)
                        rule3_ele = (x//3, y//3, ele)
    
                        rule1_list.append(rule1_ele)
                        rule2_list.append(rule2_ele)
                        rule3_list.append(rule3_ele)
    
    
            return len(set(rule1_list)) == len(rule1_list) and \
                    len(set(rule2_list)) == len(rule2_list) and \
                    len(set(rule3_list)) == len(rule3_list) # if no duplicate in list, will return True
    

    corner case 特殊情況處理

    注意要處理 “.”,不然會以為已經出現過了。

    Boundary conditions/ Edge conditions 邊際情況處理

    x

    Reference