Nil's Blog +

LeetCode 79 单词搜索 [Swift]

题目

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:

board =

[

[‘A’,’B’,’C’,’E’],

[‘S’,’F’,’C’,’S’],

[‘A’,’D’,’E’,’E’]

]

给定 word = “ABCCED”, 返回 true.

给定 word = “SEE”, 返回 true.

给定 word = “ABCB”, 返回 false.

思路

本题主要采用 ”DFS“ 和 ”回溯“ 思想进行解题,针对本地,深度优先搜索很容易想到。

整体思路是遍历二维网格每个元素,分别为起始点开始回溯:加入第一个元素,如果符合要求,继续测试下一个元素,如果第一个元素的所有子元素都不符合要求,需要回退到第一个元素的前一个元素重复上述过程。

回溯算法的逻辑实现起来相对比较绕,对照代码,我们可以关注 backtrackvalidateFirst validateNext 这三个函数是如何对回溯逻辑进行拆解的,另外关于 状态回退,也是本题需要关注的一个点,注意 marked[r][c] = false 这行即执行的回退逻辑,我们可以看到本题的解决回退的代价是比较小的。

从本题中,我们还可以总结出回溯算法的基本套路:

首先需要实现一个 backtrack 函数。

在这个函数中,首先尝试一个元素 A 是否符合约束要求,如果不符合,返回 A 不符合要求;如果符合要求,对全局状态进行修改,代表将 A 置于 目标链 (完整的目标链即为回溯类算法题的解)中,此过程即为 validateFirst

紧接着,对 A 的下一级元素 B 进行检验,递归的调用 backtrack 函数,此过程即为 validateNext,注意,如果 validateNext , 需要对全局状态进行回退,还记得全局状态是在哪步修改的么?请回顾 validateFirst 的过程找到它!

class Solution {
    
    let direction : [[Int]] = [[-1, 0], [0, -1], [0, 1], [1, 0]]
    var marked = [[Bool]]()
    var board = [[Character]]()
    var row : Int = 0
    var col : Int = 0
    var word : String = ""
    
    func exist(_ board: [[Character]], _ word: String) -> Bool {
        row = board.count
        if row == 0 {
            return false
        }
        col = board[0].count
        marked = Array(repeating: Array(repeating: false, count: col), count: row)
        self.word = word
        self.board = board
        
        for i in 0..<row {
            for j in 0..<col {
                if backtrack(i, j, 0) {
                    return true
                }
            }
        }
        return false
    }
    
    func backtrack(_ r : Int, _ c : Int, _ index : Int) -> Bool{
        if !validateFirst(r, c, index) {
            return false
        }
        if !validateNext(r, c, index) {
            marked[r][c] = false
        } else {
            return true
        }
        return false
    }
    
    func validateFirst(_ r : Int, _ c : Int, _ index : Int) -> Bool {
        let strChar = Character(word.subString(index, 1))
        let boradChar = board[r][c]
        if strChar == boradChar {
            marked[r][c] = true
        }
        return marked[r][c]
    }
    
    func validateNext(_ r : Int, _ c : Int, _ index : Int) -> Bool {
        if (index == word.count - 1) {
            return true
        }
        
        for d in 0..<4 {
            let newR = r + direction[d][0]
            let newC = c + direction[d][1]
            if inArea(newR, newC) && !marked[newR][newC] {
                if backtrack(newR, newC, index + 1) {
                    return true
                }
            }
        }
        return false
    }
    
    func inArea(_ r : Int, _ c : Int) -> Bool {
        return r >= 0 && r < row && c >= 0 && c < col
    }
}

extension String {
    func subString(_ start : Int, _ length : Int = -1) -> String {
        var len = length
        if len == -1 {
            len = self.count - start
        }
        let st = self.index(startIndex, offsetBy:start)
        let en = self.index(st, offsetBy:len)
        return String(self[st ..< en])
    }
}

回溯算法通常与 DFS ,减枝 结合进行解题,如果攻克下本题后,觉得意犹未尽,接下来可以挑战一下下面的题目 : )

力扣#235.解数独

技术

算法

项目