[LeetCode題解]79. 單詞搜尋

大雜草發表於2020-09-09

題目描述

79. 單詞搜尋

題目: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、回溯來解決。
  • 一般來說,回溯和剪枝是一起使用的,在優化時間複雜度時,記得考慮剪枝。

相關文章