返回

LeetCode:揭秘 HOT 100 经典题目“不同路径”的解题思路与优化策略

前端

  1. 暴力递归

暴力递归是解决“不同路径”问题的最直观的方法,它的思路非常简单:从起点出发,你可以向右走或者向下走,每走一步,就递归地计算从该点到终点的不同路径数,最后将所有路径数相加即可。

function numDistinctPaths(grid) {
    if (grid[0][0] === 1) {
        return 0;
    }
    return numPathsFromCell(grid, 0, 0);
}

function numPathsFromCell(grid, row, col) {
    if (row === grid.length - 1 && col === grid[0].length - 1) {
        return 1;
    }
    let numPaths = 0;
    if (row + 1 < grid.length && grid[row + 1][col] === 0) {
        numPaths += numPathsFromCell(grid, row + 1, col);
    }
    if (col + 1 < grid[0].length && grid[row][col + 1] === 0) {
        numPaths += numPathsFromCell(grid, row, col + 1);
    }
    return numPaths;
}

但是,暴力递归有一个致命的问题:当网格很大时,它会产生大量的重复计算,导致时间复杂度呈指数级增长。

2. 动态规划

为了解决暴力递归的效率问题,我们可以引入动态规划的思想。动态规划的本质是将大问题分解成若干个子问题,然后通过子问题的解来逐步解决大问题。对于“不同路径”问题,我们可以将网格划分为若干个小格子,然后计算每个小格子的不同路径数。当我们计算某个小格子的不同路径数时,我们可以直接使用已经计算过的子问题的解,从而避免重复计算。

function numDistinctPaths(grid) {
    if (grid[0][0] === 1) {
        return 0;
    }
    let dp = Array(grid.length).fill(0).map(() => Array(grid[0].length).fill(0));
    dp[0][0] = 1;
    for (let row = 0; row < grid.length; row++) {
        for (let col = 0; col < grid[0].length; col++) {
            if (grid[row][col] === 1) {
                dp[row][col] = 0;
            } else {
                if (row - 1 >= 0) {
                    dp[row][col] += dp[row - 1][col];
                }
                if (col - 1 >= 0) {
                    dp[row][col] += dp[row][col - 1];
                }
            }
        }
    }
    return dp[grid.length - 1][grid[0].length - 1];
}

动态规划法的时间复杂度为 O(mn),其中 m 和 n 分别是网格的行数和列数。空间复杂度也为 O(mn),因为我们需要存储每个小格子的不同路径数。

3. 记忆化搜索

记忆化搜索是一种优化动态规划的技巧。它通过将子问题的解存储起来,避免重复计算。对于“不同路径”问题,我们可以将计算过的子问题的解存储在哈希表中。当我们再次遇到同一个子问题时,我们就可以直接从哈希表中获取其解,从而节省大量的计算时间。

function numDistinctPaths(grid) {
    if (grid[0][0] === 1) {
        return 0;
    }
    let memo = {};
    return numPathsFromCell(grid, 0, 0, memo);
}

function numPathsFromCell(grid, row, col, memo) {
    if (row === grid.length - 1 && col === grid[0].length - 1) {
        return 1;
    }
    let key = row + ',' + col;
    if (memo[key] !== undefined) {
        return memo[key];
    }
    let numPaths = 0;
    if (row + 1 < grid.length && grid[row + 1][col] === 0) {
        numPaths += numPathsFromCell(grid, row + 1, col, memo);
    }
    if (col + 1 < grid[0].length && grid[row][col + 1] === 0) {
        numPaths += numPathsFromCell(grid, row, col + 1, memo);
    }
    memo[key] = numPaths;
    return numPaths;
}

记忆化搜索法的