[特殊字符] LeetCode 62. 不同路径 | 动态规划+递归优化详解

news/2025/2/22 17:01:14

在解 LeetCode 的过程中,路径计数问题是动态规划中一个经典的例子。今天我来分享一道非常基础但极具代表性的题目——不同路径。不仅适合初学者入门 DP(动态规划),还能帮助你打下递归思维的基础。

本文将介绍:

  1. 🔍 问题描述
  2. 💡 解题思路(包括递归+记忆化搜索)
  3. 🏆 代码实现与优化
  4. 📊 时间复杂度 & 空间复杂度分析
  5. 🔥 进阶思考

🔍 问题描述

一个机器人位于一个 m x n 的网格左上角(起点 Start)。

机器人每次只能向 移动一步,试图到达网格的右下角(终点 Finish)。

请问从起点到终点总共有多少条不同的路径?

✅ 示例

示例 1:

输入: m = 3, n = 7
输出: 28

示例 2:

输入: m = 3, n = 2
输出: 3
解释: 
1. 向右 -> 向下 -> 向下  
2. 向下 -> 向下 -> 向右  
3. 向下 -> 向右 -> 向下

示例 3:

输入: m = 7, n = 3
输出: 28

示例 4:

输入: m = 3, n = 3
输出: 6

💡 解题思路

1️⃣ 递归 + 记忆化搜索(自顶向下)

我们可以把每一步的选择抽象成一个状态转移问题:

  • 如果机器人在 (i, j) 位置,它可以从 上面 (i-1, j)左边 (i, j-1) 走过来。
  • 到达 (i, j) 的总路径数等于从 (i-1, j)(i, j-1) 走过来的路径数之和。

状态转移方程:

dp[i][j] = dp[i-1][j] + dp[i][j-1]

边界条件:

  • 第一行和第一列上的每个位置的路径数都是 1(因为只能往一个方向走)。

为什么需要记忆化?
如果不加记忆化,递归会重复计算相同子问题,时间复杂度会指数级上升。通过记忆化存储已经计算过的结果,避免重复计算,大大降低了复杂度。


🏆 代码实现(Java)

class Solution {
    public int uniquePaths(int m, int n) {
        // 创建一个记忆数组,存储子问题的解
        int[][] memo = new int[m][n];
        return dfs(m - 1, n - 1, memo);
    }

    // 递归搜索函数,i 表示行数,j 表示列数
    private int dfs(int i, int j, int[][] memo) {
        // 边界情况,越界直接返回 0
        if (i < 0 || j < 0) {
            return 0;
        }
        // 如果到达起点 (0,0),只有 1 条路径
        if (i == 0 && j == 0) {
            return 1;
        }
        // 如果该位置已经计算过,直接返回记忆值
        if (memo[i][j] != 0) {
            return memo[i][j];
        }
        // 从上面和左边的路径数之和
        return memo[i][j] = dfs(i - 1, j, memo) + dfs(i, j - 1, memo);
    }
}

📊 时间复杂度 & 空间复杂度分析

  • 时间复杂度: O(m * n)
    每个位置只会被访问一次,避免了重复计算。

  • 空间复杂度: O(m * n)
    使用了一个二维数组来保存子问题的解。


🔥 进阶思考:动态规划(自底向上)

除了递归+记忆化,还可以使用**动态规划(DP)**的方式自底向上求解,避免了递归的栈消耗。

代码实现(DP):

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];

        // 初始化边界条件
        for (int i = 0; i < m; i++) dp[i][0] = 1;
        for (int j = 0; j < n; j++) dp[0][j] = 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];
    }
}

时间复杂度: O(m * n)
空间复杂度: O(m * n)(可以优化到 O(n),只用一维数组)


📚 其他进阶解法(组合数学)

如果你喜欢数学,可以用组合数的公式来解这道题:

  • 一共需要移动 m-1 步向下,n-1 步向右。
  • 总共 m+n-2 步,从中选择 m-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)! \cdot (n - 1)!}

Java 实现:

class Solution {
    public int uniquePaths(int m, int n) {
        long res = 1;
        for (int i = 1; i <= m - 1; i++) {
            res = res * (n - 1 + i) / i;
        }
        return (int) res;
    }
}

时间复杂度: O(min(m, n))
空间复杂度: O(1)


🎯 总结

  • 🚀 使用 递归+记忆化搜索 解决子问题,避免重复计算。
  • 🏆 使用 动态规划 解决自底向上的问题,避免递归栈溢出。
  • 组合数学 提供最优解法,时间复杂度低,适合大规模输入。

如果你觉得这篇文章对你有帮助,别忘了点赞👍、收藏⭐和关注👀!欢迎在评论区和我交流更多动态规划的问题!


📢 更多 LeetCode 动态规划题解,敬请期待!


http://www.niftyadmin.cn/n/5862555.html

相关文章

python flask 使用教程 快速搭建一个 Web 应用

目录 一、Flask 简介二、Flask 安装三、创建一个简单的 Flask 应用四、Flask 路由与视图五、接收和处理用户输入六、模板引擎 Jinja2七、Flask 与数据库八、总结 一、Flask 简介 Flask 是一个轻量级的 Python Web 框架&#xff0c;旨在帮助开发者快速搭建 Web 应用。相比于 Dj…

为什么在 TypeScript 中需要使用 import type?——以 Babylon.js 为例

在 TypeScript 开发中&#xff0c;我们经常会遇到需要导入类型定义的情况。为了优化代码体积和提高开发效率&#xff0c;TypeScript 3.8 引入了 import type 语法。本文将详细介绍 import type 的作用、使用场景&#xff0c;并以 Babylon.js 为例&#xff0c;列出常见的使用 im…

渲染 101 平台 3ds Max 建筑动画渲染全攻略:费用与时间

制作 30 秒 3ds Max 建筑动画&#xff0c;渲染 101 平台不同机器配置的花费和时长差别可不小。咱们一起来算笔明白账&#xff0c;让你快速掌握成本与效率的平衡点。 一、16 核心 64G 运行内存机器 单价&#xff1a;每小时收费 1.06 元。单帧渲染时间&#xff1a;5 分钟。单帧…

C++STL容器之map

1.介绍 map是 C 标准模板库&#xff08;STL&#xff09;中的一个关联容器&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。map中的元素是按照键&#xff08;key&#xff09;进行排序的&#xff0c;并且每个键在容器中是唯一的。map通常基于红黑树&#xf…

初级银行从业考试真题

2023 年 6 月初级银行从业考试真题 法律法规 单选题 1.按照《中华人民共和国反洗钱法》的规定,金融机构所建立的客户身份资料和客户交易信息在业务关系或交易结束后至少 保存期限为()年。 A.5 B.3 C.10 D.2 参考答案:A 2.物价稳定是要保持()的大体稳定,避免出现高…

Prompt:创造性的系统分析者

分享的提示词&#xff1a; 你是一个创造性的系统分析者&#xff0c;作为咨询师&#xff0c;你具有以下特质&#xff1a; 基础能力&#xff1a; 深入理解我的系统性模式 识别模式间的隐藏联系 发现出人意料的关联 提供令人惊讶的洞见 工作方式&#xff1a; 在每次回应中至少…

【Js】getBoundingClientRect()

getBoundingClientRect是一个非常有用的Web API&#xff0c;它用于获取一个元素的大小及其相对于视口的位置。以下是对该方法的详细解释&#xff0c;包括其功能、用法和示例。 功能 getBoundingClientRect方法&#xff1a;不接受任何参数&#xff0c;返回一个DOMRect对象&…

已知点矩阵的三个顶点坐标、行列数和行列的间距,计算得出剩余所有点的坐标

已知点矩阵的三个顶点坐标、行列数和行列的间距&#xff0c;计算得出剩余所有点的坐标 计算矩阵中每个点的坐标代码实现案例图调用验证 计算矩阵中每个点的坐标 给定左上角、左下角和右上角三个点的坐标&#xff0c;以及矩阵的行数、列数、行间距和列间距&#xff0c;我们可以…