Leetcode 的強大之處,挺多的。
本文寫的是,其強大的討論區。
討論區裡面,有各種具有啟發性的程式碼。
(換句話說,就是有很強的程式碼。看了,覺得腦洞大開,大神們把語言的語法特性發揮到了極致)
裡面有各種常見語言的實現
( 這裡指 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 我的程式碼
相關程式碼: github.com/BoxDengJZ/l…
強大的程式碼: Python 實現