从递归入手二维DP(下)

78 阅读3分钟

所有的递归都需要改成动态规划吗?

答案是 不需要。

能改成动态规划的递归,统一特征:

​ 决定返回值的可变参数类型往往都比较简单,一般不会比int类型更复杂。

在前面的 64. 最小路径和 问题中,可变参数是当前位置iji,jint型变量,很方便我们建立DP二维表格。

看下面的这道题,对于参数比较复杂的递归问题,比如 需要记录路径的递归 ,就不方便改为动态规划。

LeetCode 79. 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

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

示例 1:

img

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

提示:

  • m == board.length
  • n = board[i].length
  • 1 <= m, n <= 6
  • 1 <= word.length <= 15
  • boardword 仅由大小写英文字母组成

**进阶:**你可以使用搜索剪枝的技术来优化解决方案,使其在 board 更大的情况下可以更快解决问题?


问题分析:

一个没有学习过递归和DFS算法的人遇到这个问题的思路是这样的:

​ 先找到所有值为word[0]的格子,之后对这些格子,每个格子都作为开头去尝试一遍,尝试的步骤:

​ 1.查看旁边有没有值为word[i+1]的格子,如果有,则走到word[i+1]的格子,之后重新尝试

​ 2.如果没有值为word[i+1]的格子,则这条路走不通,尝试其他路。

现在我们了解了递归,那我们可以把这个思路转化为递归的思路:

​ 定义f(i,j,k)为将(i,j)格子作为开头能否匹配出(探索出)字符串word[k,...],如果能,则f(i,j,k)记为true,否则记为false。有了这个定义式,状态转移式子也很容易得出来:

f(i,j,k)=f(i1,j,k+1)    f(i+1,j,k+1)    f(i,j1.k+1)    f(i,j+1,k+1)  f(i,j,k) = f(i-1,j,k+1) ~~ || ~~ f(i+1,j,k+1) ~~|| ~~ f(i,j-1.k+1) ~~|| ~~ f(i,j+1,k+1)~~

递归的结束条件是:

​ 匹配到最后一个字符并且匹配成功。即

grid[i][j]==word[k],k=word.size()1grid[i][j] == word[k], k =word.size()-1

最后要注意的是,但凡是 搜索问题, 都要防止“走回头路”,搜过的点(可以理解成搜索过的状态)不能再搜,所以一般来说我们都要采取一些辅助措施来避免“走回头路”。

解决代码:
static constexpr int directions[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool exist(vector<vector<char>>& board, string word) {
    int rows = board.size();
    int columns = board[0].size();
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < columns; j++) {
            if (board[i][j] == word[0])
                if (f(i, j, 0, board, word))
                    return true;
        }
    }
    return false;
}
bool f(int i, int j, int k, vector<vector<char>>& board, string word) {
    if (board[i][j] != word[k]) // 此节点不符合要求,不能再延伸搜索
        return false;
    if (k == word.size() - 1) //匹配成功
        return true;

    // 表示节点(i,j)在路径上
    board[i][j] = '0';
    // 向周边节点扩散
    for (auto& direction : directions) {
        int next_i = i + direction[0];
        int next_j = j + direction[1];

        if (next_i >= 0 && next_i < board.size() && next_j >= 0 &&
            next_j < board[0].size() &&
            f(next_i, next_j, k + 1, board, word)) {
            board[i][j] = word[k];
            return true;
        }
    }

    // 恢复原样
    board[i][j] = word[k];
    return false;
}

现在来重新思考一下之前的问题,这段代码能改成二维DP的代码吗?

​ 答案是不能,因为我们需要时刻记录当前的路径,DP做不到这一点。所以参数类型比较复杂的递归,没有必要改成DP。