Leetcode 的強大之處 演算法題解 in Swift ( 有效的數獨 , 36 ) 及其 Code Review
討論區裡面,有各種具有啟發性的程式碼。
(換句話說,就是有很強的程式碼。看了,覺得腦洞大開,大神們把語言的語法特性發揮到了極致)
裡面有各種常見語言的實現
( 這裡指 Leetcode 主站的, 中文站點的同一功能弱了一點 )
進入 Leetcode 的題目,

進入討論區,裡面的討論挺多的,這道題就有 470 個帖子。
本文推薦選擇 "Most Votes",
事實就是得分高的程式碼,看起來爽

也可以搜尋一下自己關心的, 一般是按語言來搜尋。

就 Leetcode 演算法題,本文認為有了 Leetcode 的討論區,和官方題解 (有些沒有,最近的題都有)
其他的資料...... ,都好像有些弱 (不喜歡英語的少年,除外)
題目描述: 36. 有效的數獨
判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。
數字 1-9 在每一行只能出現一次。 數字 1-9 在每一列只能出現一次。 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。

上圖是一個部分填充的有效的數獨。
數獨部分空格內已填入了數字,空白格用 '.' 表示。 示例 :
輸入:

輸出: true
題解 ( 改進前):
下面的解法,非常直觀, 根據數獨的成立條件,分三次檢查數字,按行,按列,按塊
(先橫著來,再豎著來,最後一塊一塊來)
因為數獨有數字的唯一性,這裡使用雜湊集合
每一次處理,通過的情況分兩種,
掃描一輪,1-9 剛剛好。或者含有 ''.", 其他的數字各不相同。
class Solution { func isValidSudoku(_ board: [[Character]]) -> Bool { let count = board.count var set = Set<Character>() for i in 0..<count{ //橫著來,按行,檢查數字 set = Set(board[i]) var num = board[i].reduce(0 , {(result : Int, char : Character) in var cent = 0 if String(char) == "."{ cent = 1 } return result + cent }) //每一次處理,通過本次迴圈的情況分兩種, //掃描一輪,1-9 剛剛好。或者含有 ".", 其他的數字各不相同。 //這裡做了一個針對處理 if num > 0 , count - num != set.count - 1{ return false } else if num == 0, set.count != count{ return false } // 豎著來,按列,檢查數字 set = Set(board.reduce([Character]() , { resultArray, chars in return resultArray + [chars[i]] })) num = board.reduce(0 , {(result : Int, chars : [Character]) in var cent = 0 if String(chars[i]) == "."{ cent = 1 } return result + cent }) if num > 0 , count - num != set.count - 1{ return false } else if num == 0, set.count != count{ return false } // 一塊一塊來, 按塊,檢查數字 let characters = board.flatMap{ return $0 } let fisrtMiddle = ( i/3 ) * 27 + ( i % 3 ) * 3 + 1 let secondMiddle = fisrtMiddle + 9 let thirdMiddle = fisrtMiddle + 18 // 找出每一塊 let arrayThree = [characters[fisrtMiddle - 1], characters[fisrtMiddle], characters[fisrtMiddle + 1], characters[secondMiddle - 1], characters[secondMiddle], characters[secondMiddle + 1], characters[thirdMiddle - 1], characters[thirdMiddle], characters[thirdMiddle + 1]] set = Set(arrayThree) num = arrayThree.reduce(0 , {(result : Int, char : Character) in var cent = 0 if String(char) == "."{ cent = 1 } return result + cent }) if num > 0 , count - num != set.count - 1{ return false } else if num == 0, set.count != count{ return false } } return true } } 複製程式碼
Code Review :
演算法上的提高
沒必要計算 "." 的個數。先處理資料,把 "." 過濾掉, 再建立雜湊集合。
按行,按列,按塊查詢重複數字,就直觀了很多。不需要考慮 "." 的干擾。
命名要規範,
怎麼知道 set
裡面包含什麼?
不清晰, 不知道 num
是記錄的是什麼的數量。
cent
和 arrayThree
是什麼鬼?
改進程式碼
if String(char) == "."
, 可以直接寫成 if char == ".”
Swift 中 "." 是字串的字面量,也是字元的字面量。不需要把字元轉化為字串。
改進閉包
按行,計算一次迴圈,不包含 "." 的
var num = board[i].reduce(0 , {(result : Int, char : Character) in var cent = 0 if String(char) == "."{ cent = 1 } return result + cent }) 複製程式碼
1⃣️ 簡寫閉包,用三目,減少臨時變數
var num = board[i].reduce(0 , {(result, char) in char == "." ? result + 1 : result }) 複製程式碼
這樣處理更高效
先把資料處理乾淨,過濾 "."
let rowDigits = board[i].filter { $0 != "." } if rowDigits.count != Set(rowDigits).count { return false } 複製程式碼
set = Set(board.reduce([Character]() , { resultArray, chars in return resultArray + [chars[i]] })) 複製程式碼
2⃣️ 科學型別轉換
let column = board.map { $0[i]} // Column #i set = Set(column) 複製程式碼
找出每一塊
let fisrtMiddle = ( i/3 ) * 27 + ( i % 3 ) * 3 + 1 let secondMiddle = fisrtMiddle + 9 let thirdMiddle = fisrtMiddle + 18 // 找出每一塊 let arrayThree = [characters[fisrtMiddle - 1], characters[fisrtMiddle], characters[fisrtMiddle + 1], characters[secondMiddle - 1], characters[secondMiddle], characters[secondMiddle + 1], characters[thirdMiddle - 1], characters[thirdMiddle], characters[thirdMiddle + 1]] 複製程式碼
3⃣️ 使用陣列片段 ( slice ), 發揮高階函式的威力
let firstRow = 3 * (i / 3) let firstCol = 3 * (i % 3) let block = board[firstRow..<firstRow+3].flatMap { $0[firstCol..<firstCol+3]} 複製程式碼
最後的程式碼:
class Solution { func isValidSudoku(_ board: [[Character]]) -> Bool { for i in 0..<9 { // 按行,檢查數字 let rowDigits = board[i].filter { $0 != "." } if rowDigits.count != Set(rowDigits).count { return false } // 按列,檢查數字 let colDigits = board.map { $0[i] }.filter { $0 != "." } if colDigits.count != Set(colDigits).count { return false } // 按塊,檢查數字 let firstRow = 3 * (i / 3) let firstCol = 3 * (i % 3) let blockDigits = board[firstRow..<firstRow+3].flatMap { $0[firstCol..<firstCol+3]} .filter { $0 != "." } if blockDigits.count != Set(blockDigits).count { return false } } return true } } 複製程式碼
另一種解法, 更加的函式式,效能差一些
使用 27 的雜湊集合, 對應 9 次迴圈 X 3 種方式 ( 按行, 按塊,按列)
排除掉 "." , 有重複的數字,就返回錯誤。
兩層遍歷順利完成後,返回成功。
class Solution { func isValidSudoku(_ board: [[Character]]) -> Bool { var rowSets = Array(repeating: Set<Character>(), count: 9) var colSets = Array(repeating: Set<Character>(), count: 9) var blockSets = Array(repeating: Set<Character>(), count: 9) for (i, row) in board.enumerated() { for (j, char) in row.enumerated() where char != "." { if !rowSets[i].insert(char).inserted { return false } if !colSets[j].insert(char).inserted { return false } let block = (i / 3) + 3 * (j / 3) if !blockSets[block].insert(char).inserted { return false } } } return true } } 複製程式碼
Leetcode 連結:valid-sudoku
感謝Martin R 大神code review 我的程式碼
相關程式碼: ofollow,noindex">github.com/BoxDengJZ/l…
強大的程式碼:Python 實現