題目描述
題目:79. 單詞搜尋
解題思路
遍歷
首先找重複性,題目說給定單詞是否存在於二維陣列中,可以簡化為從 (x, y) 走 n 步(n 表示單詞長度),檢視給定單詞是否存在。然後再遍歷二維陣列裡的所有點,看是否存在給定單詞。
func exist(board [][]byte, word string) bool {
for x:=0;x<n;x++ {
for y:=0;y<m;y++ {
if dfs(x, y, 0) {
return true
}
}
}
return false
}
回溯
從 (x, y) 走 n 步,每一步都可以從上下左右四個方向“試探步”,直到走完 n 步,然後再比較“走過的路徑” 和給定單詞是否相等。
func backtrack(x int, y int, index int, s *[]byte) {
// 終止條件:走完 n 步
if index == len(word) {
return string(s) == word
}
if !visited[x][y] {
visited[x][y] = true
s = append(s, board[x][y])
for i:=0;i<direction;i++ {
newX, newY := x+direction[i][0], y+direction[i][1]
if backtrack(newX, newY, index+1) {
return true
}
}
s = s[:len(s)]
visited[x][y] = false
}
return false
}
此程式碼存在問題,沒有考慮邊界的問題,當向上下左右移動時,不能超過邊界,因此程式碼調整為:
func backtrack(x int, y int, index int, s *[]byte) {
// 終止條件:走完 n 步
if index == len(word) {
return string(s) == word
}
if !visited[x][y] {
visited[x][y] = true
s = append(s, board[x][y])
for i:=0;i<direction;i++ {
newX, newY := x+direction[i][0], y+direction[i][1]
if inArea(newX, newY) && backtrack(newX, newY, index+1) {
return true
}
}
s = s[:len(s)]
visited[x][y] = false
}
return false
}
func inArea(x int, y int) bool {
return x < n && x >= 0 && y < m && y >= 0
}
剪枝
上面的程式碼可以進一步優化,在回溯過程中,可以預先判斷結果,假如走到第 i 步時,此時的字元與給定單詞的第 i 位字元不相等,則可以剪掉後續的比較,即剪掉分支。
注:回溯、dfs 本質上是遞迴,函式呼叫的過程會生成一顆遞迴樹。
func backtrack(x int, y int, index int) bool {
if index == len(word)-1 {
return board[x][y] == word[index]
}
if board[x][y] == word[index] {
visited[x][y] = true
// 遍歷四個方向
for i := 0; i < len(direction); i++ {
newX, newY := x+direction[i][0], y+direction[i][1]
if inArea(newX, newY) && !visited[newX][newY] {
if backtrack(newX, newY, index+1) {
return true
}
}
}
visited[x][y] = false
}
return false
}
func inArea(x int, y int) bool {
return x < n && x >= 0 && y < m && y >= 0
}
程式碼實現
var direction = [][]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}
var visited [][]bool
var n, m int
func exist(board [][]byte, word string) bool {
n = len(board)
if n == 0 {
return false
}
m = len(board[0])
if m == 0 {
return false
}
visited = make([][]bool, n)
for i := 0; i < n; i++ {
visited[i] = make([]bool, m)
}
for x := 0; x < n; x++ {
for y := 0; y < m; y++ {
if backtrack(board, word, 0, x, y) {
return true
}
}
}
return false
}
func backtrack(board [][]byte, word string, index int, x int, y int) bool {
if index == len(word)-1 {
return board[x][y] == word[index]
}
if board[x][y] == word[index] {
visited[x][y] = true
// 遍歷四個方向
for i := 0; i < len(direction); i++ {
newX, newY := x+direction[i][0], y+direction[i][1]
if inArea(newX, newY) && !visited[newX][newY] {
if backtrack(board, word, index+1, newX, newY) {
return true
}
}
}
visited[x][y] = false
}
return false
}
func inArea(x int, y int) bool {
return x < n && x >= 0 && y < m && y >= 0
}
複雜度分析:
- 時間複雜度:O(n * m * L),其中 n, m, L 分別表示二維陣列的行、列和給定單詞的長度。
- 最好情況,遍歷二維陣列第一個元素,且走一次就找到。
- 最壞情況,要遍歷到二維陣列的最後一個元素,並且各個方向都走完後,沒找到結果。
- 空間複雜度:O(n * m),其中 n, m 分別表示二維陣列的行、列。只需要一個二維陣列記錄是否訪問過元素。
總結
- 對於類似排列、組合的問題,第一時間要想到可以使用dfs、回溯來解決。
- 一般來說,回溯和剪枝是一起使用的,在優化時間複雜度時,記得考慮剪枝。