Leetcode 的強大之處  Swift Code Review 演算法題 ( 有效的數獨 , 36 )

鄧輕舟發表於2018-12-02

Leetcode 的強大之處,挺多的。

本文寫的是,其強大的討論區。

討論區裡面,有各種具有啟發性的程式碼。

(換句話說,就是有很強的程式碼。看了,覺得腦洞大開,大神們把語言的語法特性發揮到了極致)

裡面有各種常見語言的實現

( 這裡指 Leetcode 主站的, 中文站點的同一功能弱了一點 )

進入 Leetcode 的題目,

1.png

進入討論區,裡面的討論挺多的,這道題就有 470 個帖子。

本文推薦選擇 "Most Votes",

事實就是得分高的程式碼,看起來爽

2.png

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

3.png

就 Leetcode 演算法題,本文認為有了 Leetcode 的討論區,和官方題解 (有些沒有,最近的題都有)

其他的資料...... ,都好像有些弱 (不喜歡英語的少年,除外)


題目描述: 36. 有效的數獨

判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。

數字 1-9 在每一行只能出現一次。 數字 1-9 在每一列只能出現一次。 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。

1

上圖是一個部分填充的有效的數獨。

數獨部分空格內已填入了數字,空白格用 '.' 表示。 示例 :

輸入:

2

輸出: 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 是記錄的是什麼的數量。

centarrayThree 是什麼鬼?

改進程式碼

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 實現

相關文章