解锁网格路径问题的多解法思路:递归、动态规划与组合数公式

题目分析

机器人在一个 m x n 的网格上移动,起点在左上角 (0,0),终点在右下角 (m-1,n-1)。机器人每次只能向或者移动一步,问题是:有多少种不同的路径可以从起点到达终点?

62. 不同路径 - 力扣(LeetCode)

思考要点:
  • 机器人只能向下和向右移动,这意味着路径的选择受限,实际上就是在一个固定的路径集合中找到所有可能的路径。
  • 我们可以通过多种方式来计算路径的总数,比较经典的解法包括递归法动态规划法、以及组合数学法。接下来,我将逐一讲解这些方法。

解题思路一:递归法

递归是解决此类问题的基础方法。通过递归将问题分解为更小的子问题,最终汇总子问题的结果来得出解答。

  1. 递归关系:当机器人在位置 (i, j) 时,它可以从 (i-1, j) 或者 (i, j-1) 走到当前位置。因此,该位置的路径总数等于从它上方和左方来的路径数之和。
  2. 边界条件
    • 当机器人位于第一行时,只能从左边来(向右走)。
    • 当机器人位于第一列时,只能从上边来(向下走)。
  3. 递归终止条件
    • 当到达右下角时,递归结束,此时返回1条路径。
代码实现(递归)
#include <iostream>
using namespace std;

int uniquePaths(int m, int n) {
    
    
    if (m == 1 || n == 1) {
    
    
        return 1;
    }
    return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
}

int main() {
    
    
    int m = 3, n = 7;
    cout << "不同路径数: " << uniquePaths(m, n) << endl;
    return 0;
}
缺点分析:
  • 这种纯递归法存在效率问题。由于递归会重复计算相同的子问题,时间复杂度为指数级别 O ( 2 m + n ) O(2^{m+n}) O(2m+n),尤其在网格较大时,会导致超时。

改进:记忆化递归

为了解决递归法的效率问题,我们可以使用记忆化递归。通过存储中间结果,避免重复计算相同的子问题,使得时间复杂度大幅降低至 O(m * n)

代码实现(记忆化递归)
#include <iostream>
#include <vector>
using namespace std;

int helper(int m, int n, vector<vector<int>>& memo) {
    
    
    if (m == 1 || n == 1) {
    
    
        return 1;
    }
    if (memo[m][n] != -1) {
    
    
        return memo[m][n];
    }
    memo[m][n] = helper(m - 1, n, memo) + helper(m, n - 1, memo);
    return memo[m][n];
}

int uniquePaths(int m, int n) {
    
    
    vector<vector<int>> memo(m + 1, vector<int>(n + 1, -1));
    return helper(m, n, memo);
}

int main() {
    
    
    int m = 3, n = 7;
    cout << "不同路径数: " << uniquePaths(m, n) << endl;
    return 0;
}

解题思路二:动态规划法

在递归法中,我们看到大量重复计算的情况。动态规划的核心思想是使用表格存储中间结果,逐步填充表格来得到最终结果,从而避免递归中的重复计算问题。

  1. 状态定义dp[i][j] 表示从起点 (0, 0) 到位置 (i, j) 的路径数。
  2. 状态转移方程dp[i][j] = dp[i-1][j] + dp[i][j-1],即该位置的路径数等于从它的上方和左方来的路径数之和。
  3. 初始条件
    • 第一行和第一列的路径数为1,因为这些位置只能从一个方向走到。
  4. 边界条件:只需考虑网格边界外的路径初始化。
代码实现(动态规划)
#include <iostream>
#include <vector>
using namespace std;

int uniquePaths(int m, int n) {
    
    
    vector<vector<int>> dp(m, vector<int>(n, 1));
    
    for (int i = 1; i < m; i++) {
    
    
        for (int j = 1; j < n; j++) {
    
    
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }
    
    return dp[m - 1][n - 1];
}

int main() {
    
    
    int m = 3, n = 7;
    cout << "不同路径数: " << uniquePaths(m, n) << endl;
    return 0;
}
动态规划的优化:滚动数组
  • 动态规划法的空间复杂度是 O(m * n),但实际上我们只需要存储当前行和上一行的值。因此,我们可以用滚动数组将空间复杂度优化为 O(n)
滚动数组优化后的代码:
#include <iostream>
#include <vector>
using namespace std;

int uniquePaths(int m, int n) {
    
    
    vector<int> dp(n, 1);
    
    for (int i = 1; i < m; i++) {
    
    
        for (int j = 1; j < n; j++) {
    
    
            dp[j] += dp[j - 1];
        }
    }
    
    return dp[n - 1];
}

int main() {
    
    
    int m = 3, n = 7;
    cout << "不同路径数: " << uniquePaths(m, n) << endl;
    return 0;
}

解题思路三:组合数学法

除了动态规划,还可以使用组合数学的方法解决。机器人需要总共走 m-1 步向下和 n-1 步向右,总共需要走 m+n-2 步。因此,问题可以转化为从这些步数中选择 m-1 步向下的组合数问题。

  1. 组合数公式为:
    C ( m + n − 2 , m − 1 ) = ( m + n − 2 ) ! ( m − 1 ) ! ( n − 1 ) ! C(m+n-2, m-1) = \frac{(m+n-2)!}{(m-1)!(n-1)!} C(m+n2,m1)=(m1)!(n1)!(m+n2)!
  2. 优化建议:由于直接计算阶乘容易导致溢出,我们可以通过迭代计算组合数,避免溢出。
代码实现(组合数学法)
#include <iostream>
using namespace std;

long long combination(int m, int n) {
    
    
    long long result = 1;
    int k = min(m, n);
    for (int i = 1; i <= k; i++) {
    
    
        result = result * (m + n - i) / i;
    }
    return result;
}

int uniquePaths(int m, int n) {
    
    
    return combination(m - 1, n - 1);
}

int main() {
    
    
    int m = 3, n = 7;
    cout << "不同路径数: " << uniquePaths(m, n) << endl;
    return 0;
}

代码效率分析

  1. 递归法

    • 时间复杂度:O(2^{m+n}),由于大量重复计算,效率最低。
    • 空间复杂度:O(m+n),由于递归调用栈占用空间。
  2. 记忆化递归法和动态规划法

    • 时间复杂度:O(m*n),通过存储中间结果,显著减少了重复计算。
    • 空间复杂度:记忆化递归和普通动态规划都是 O(m*n),滚动数组优化的动态规划则是 O(n)
  3. 组合数学法

    • 时间复杂度:O(m+n),通过组合数公式快速计算路径数。
    • 空间复杂度:O(1),仅需常数级空间存储结果。

总结

  • 递归法适合初学者理解递归和问题分解的思想,但效率不高。
  • 动态规划法是解决此类问题的常用方法,通过表格存储中间结果,能处理较大规模数据。
  • 组合数学法直接运用组合数公式,提供最优解,时间和空间复杂度都最小,但需要一定的数学基础。

猜你喜欢

转载自blog.csdn.net/qq_22841387/article/details/143087895