【递归、搜索与回溯算法】(掌握记忆化搜索的核心套路)


🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在算法学习的过程中,递归、搜索与回溯几乎是每位学习者都绕不开的核心主题.它们不仅频繁出现在基础题和面试题中,也是理解更高级算法思想的重要入口.很多看似复杂的问题,拆开之后,本质上都是在一棵"决策树"上不断尝试、回退、剪枝,最终找到答案.不过,真正让不少人感到困惑的,并不是递归本身,而是:什么时候该搜索,什么时候该回溯,什么时候又该引入记忆化搜索来优化?"同样是"从一个状态出发不断往下尝试",有些题直接暴力递归就能解决,有些题却会因为大量重复计算而效率极低.这时候,记忆化搜索就成了连接"暴力搜索"和"动态规划"之间的一座桥梁.可以说,掌握记忆化搜索的核心套路,不仅能帮助我们更高效地解决递归类问题,也能让我们对状态设计、重复子问题、搜索剪枝等关键思想形成更系统的理解.它不是单纯的"加一个缓存"这么简单,而是一种帮助我们从"会写递归"走向"会优化搜索"的思维升级.本文将围绕递归、搜索与回溯算法展开,重点讲清楚记忆化搜索的本质、适用场景与通用解题模板,帮助你真正建立起一套可迁移、可复用的解题框架.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!

目录

1.记忆化搜索的算法思想背景

在正式理解记忆化搜索之前,我们要先明白一个很关键的问题:为什么普通递归会慢?很多初学者第一次接触递归时,往往会觉得这种写法很优雅.因为它天然符合"把大问题拆成小问题"的思考方式:一个问题如果可以由若干个更小的同类问题组成,那么就可以先定义递归函数,再不断向下求解,直到触达边界条件.这种思想本身没有问题,问题出在------重复计算.

举个最经典的例子,假设我们用递归去求斐波那契数列.为了计算f(n),我们需要先计算f(n-1)和f(n-2);而在计算 f(n-1) 的过程中,又会继续计算 f(n-2) 和 f(n-3).这样一来,像 f(n-2) 这样的子问题就会被反复求解很多次.随着n的增大,这种重复会迅速膨胀,最终让时间复杂度呈指数级增长.

这说明了一个重要事实:

很多递归问题并不是不能做,而是"做了太多遍同样的事".

而记忆化搜索的出现,本质上就是为了解决这个问题.
从"暴力展开"到"结果复用"

记忆化搜索的核心思想并不复杂,可以概括成一句话:每个状态只计算一次,算过的结果直接保存,下次再遇到时直接返回.也就是说,递归仍然保留,搜索过程也仍然存在,但我们不再让程序无休止地重复进入同一个子问题.第一次算出答案后,就把它"记住".以后只要搜索再次来到这个状态,就不必重新展开递归树,而是直接取出之前保存的结果.这就是"记忆化"三个字的含义:让算法拥有"记住过去"的能力.

从思想上看,它其实是在暴力搜索的基础上加入了一层"缓存机制",从而把原本大量重复的计算折叠掉.于是,一棵原本会疯狂生长的递归树,被压缩成了一张更有结构的"状态图".

记忆化搜索背后的适用前提

并不是所有递归都适合记忆化搜索.它之所以成立,通常依赖两个前提:

  • 问题具有最优子结构或可拆分结构.
    也就是说,大问题的答案可以通过若干个小问题的答案组合得到.
  • 问题中存在重复子问题.
    如果每次递归都会走到完全不同的状态,没有任何重复,那就没有"记住"的必要,缓存也起不到优化作用.所以,记忆化搜索最典型的使用场景,就是那些"递归很好想,但直接写会超时"的题目.比如:
  • 斐波那契数列
  • 爬楼梯
  • 网格路径问题
  • 字符串拆分
  • 背包类搜索问题
  • 区间递归问题
  • 博弈型状态搜索问题

这些题目的共同点都是:状态会重复出现,而重复状态的答案又是固定的.

本质总结

如果用一句更凝练的话来概括,记忆化搜索的算法思想背景就是:它诞生于暴力递归的低效之中,本质是用"空间换时间"的方式,消除搜索过程中的重复计算.它既继承了递归"自顶向下、天然分解问题"的直观性,又吸收了动态规划"保存状态、复用结果"的高效性.因此,它不是一个孤立的技巧,而是一种非常重要的算法过渡思想.


2.斐波那契数(OJ题)


算法思路:解法(暴搜 -> 记忆化搜索 -> 动态规划):
暴搜:

a. 递归含义:给 dfs 一个使命,给他一个数 n,返回第 n 个斐波那契数的值;

b. 函数体:斐波那契数的递推公式;

c. 递归出口:当 n == 0 或者 n == 1 时,不用套公式.

记忆化搜索:

a. 加上一个备忘录;

b. 每次进入递归的时候,去备忘录里面看看;

c. 每次返回的时候,将结果加入到备忘录里面.

动态规划:

a. 递归含义 -> 状态表示;

b. 函数体 -> 状态转移方程;

c. 递归出口 -> 初始化.

核心代码

c 复制代码
//全局数组:记忆化搜索的备忘录,存储已经计算过的斐波那契数,避免重复递归计算
int memo[31];
//全局数组:动态规划的dp数组,存储递推计算的斐波那契数
int dp[31];

//动态规划解法(迭代/自底向上):计算斐波那契数列第n项
int fib(int n)
{
    //初始化动态规划的基础条件:斐波那契第0项为0,第1项为1
    dp[0] = 0;
    dp[1] = 1;
    //从第2项开始,循环递推计算到第n项
    for(int i = 2; i <= n; i++)
        //核心递推公式:当前项 = 前一项 + 前两项
        dp[i] = dp[i - 1] + dp[i - 2];
    //返回最终计算的第n项斐波那契数
    return dp[n];
}

//记忆化搜索解法(递归+备忘录/自顶向下):计算斐波那契数列第n项
int dfs(int n)
{
    //第一步:检查备忘录,如果当前值已经计算过(不等于-1),直接返回结果
    if(memo[n] != -1)
    {
        return memo[n];
    }
    //第二步:递归终止条件(基础情况)
    if(n == 0 || n == 1)
    {
        memo[n] = n;  //将结果存入备忘录
        return n;     //返回基础值
    }
    //第三步:递归计算,将结果存入备忘录,避免重复计算
    memo[n] = dfs(n - 1) + dfs(n - 2);
    //返回最终计算结果
    return memo[n];
}

//主函数:测试两种解法
int main()
{
    //记忆化搜索必须初始化!将备忘录全部赋值为-1,表示未计算
    for(int i = 0; i < 31; i++){
        memo[i] = -1;
    }

    //测试:计算斐波那契第10项
    int n = 10;
    printf("动态规划计算结果:%d\n", fib(n));
    printf("记忆化搜索计算结果:%d\n", dfs(n));
    return 0;
}

完整测试代码

c 复制代码
#include <iostream>
#include <cstring>
using namespace std;

int memo[31]; // 备忘录
int dp[31];

// 动态规划
int fib(int n)
{
    dp[0] = 0;
    dp[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

// 记忆化搜索
int dfs(int n)
{
    if (memo[n] != -1)
    {
        return memo[n]; // 直接去备忘录里面拿值
    }

    if (n == 0 || n == 1)
    {
        memo[n] = n; // 记录到备忘录里面
        return n;
    }

    memo[n] = dfs(n - 1) + dfs(n - 2); // 记录到备忘录里面
    return memo[n];
}

int main()
{
    int n;
    cout << "请输入 n (0~30): ";
    cin >> n;

    if (n < 0 || n > 30)
    {
        cout << "输入范围错误,请输入 0~30 之间的整数。" << endl;
        return 0;
    }

    // 初始化 memo 数组为 -1
    memset(memo, -1, sizeof(memo));

    cout << "动态规划结果: " << fib(n) << endl;
    cout << "记忆化搜索结果: " << dfs(n) << endl;

    return 0;
}

3.不同路径(OJ题)


算法思路:解法(暴搜 -> 记忆化搜索 -> 动态规划):
暴搜:

a. 递归含义:给 dfs 一个使命,给他一个下标,返回从 [0, 0] 位置走到 [i, j] 位置一共有多少种方法;

b. 函数体:只要知道到达上面位置的方法数以及到达左边位置的方法数,然后累加起来即可;

c. 递归出口:当下标越界的时候返回 0;当位于起点的时候,返回 1.

记忆化搜索:

a. 加上一个备忘录;

b. 每次进入递归的时候,去备忘录里面看看;

c. 每次返回的时候,将结果加入到备忘录里面.

动态规划:

a. 递归含义 -> 状态表示;

b. 函数体 -> 状态转移方程;

c. 递归出口 -> 初始化.

核心代码

cpp 复制代码
class Solution
{
public:
    //主函数:入口,传入网格行数m、列数n,返回总路径数
    int uniquePaths(int m, int n)
    {
        //方法1:动态规划(迭代/自底向上)
        //定义dp二维数组:dp[i][j] 表示从起点(1,1)到达位置(i,j)的路径总数
        //数组大小 (m+1) x (n+1),方便从1开始索引(对应网格坐标)
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        
        //初始化起点:(1,1) 是起始位置,只有1种路径(原地不动)
        dp[1][1] = 1;

        //遍历网格所有行 i
        for(int i = 1; i <= m; i++)
            //遍历网格所有列 j
            for(int j = 1; j <= n; j++)
            {
                //跳过起点,因为起点已经初始化
                if(i == 1 && j == 1) continue;
                
                //核心状态转移方程:
                //到达(i,j) 只能从 上方(i-1,j) 或 左方(i,j-1) 走过来
                //所以路径数 = 上方路径数 + 左方路径数
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        
        //返回终点(m,n)的总路径数
        return dp[m][n];

        //方法2:记忆化搜索(递归/自顶向下)
        //定义备忘录数组:存储已计算的结果,避免重复递归
        //vector<vector<int>> memo(m + 1, vector<int>(n + 1));
        //调用递归函数,返回结果
        //return dfs(m, n, memo);
    }

    //递归函数:记忆化搜索,计算从(1,1)到(i,j)的路径数
    //memo:备忘录数组(引用传递,节省空间+同步修改)
    int dfs(int i, int j, vector<vector<int>>& memo)
    {
        //第一步:查备忘录!如果当前位置已经计算过,直接返回结果(剪枝,避免重复计算)
        if(memo[i][j] != 0)
        {
            return memo[i][j];
        }

        //边界条件1:越界(行/列=0),没有路径,返回0
        if(i == 0 || j == 0) return 0;
        
        //边界条件2:到达起点,路径数=1
        if(i == 1 && j == 1)
        {
            memo[i][j] = 1;  //存入备忘录
            return 1;
        }

        //核心递归逻辑:
        //路径数 = 从上方来的路径 + 从左方来的路径
        //计算后存入备忘录,方便后续直接调用
        memo[i][j] = dfs(i - 1, j, memo) + dfs(i, j - 1, memo);
        
        //返回当前位置的总路径数
        return memo[i][j];
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

class Solution
{
public:
    // 动态规划
    int uniquePaths(int m, int n)
    {
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        dp[1][1] = 1;

        for (int i = 1; i <= m; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                if (i == 1 && j == 1) continue;
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }

        return dp[m][n];
    }

    // 记忆化搜索入口
    int uniquePathsMemo(int m, int n)
    {
        vector<vector<int>> memo(m + 1, vector<int>(n + 1, 0));
        return dfs(m, n, memo);
    }

    int dfs(int i, int j, vector<vector<int>>& memo)
    {
        if (memo[i][j] != 0)
        {
            return memo[i][j];
        }

        if (i == 0 || j == 0) return 0;

        if (i == 1 && j == 1)
        {
            memo[i][j] = 1;
            return 1;
        }

        memo[i][j] = dfs(i - 1, j, memo) + dfs(i, j - 1, memo);
        return memo[i][j];
    }
};

int main()
{
    Solution s;

    int m, n;
    cout << "请输入网格大小 m 和 n: ";
    cin >> m >> n;

    if (m <= 0 || n <= 0)
    {
        cout << "m 和 n 必须大于 0。" << endl;
        return 0;
    }

    int ans1 = s.uniquePaths(m, n);
    int ans2 = s.uniquePathsMemo(m, n);

    cout << "动态规划结果: " << ans1 << endl;
    cout << "记忆化搜索结果: " << ans2 << endl;

    return 0;
}

4.最长递增子序列(OJ题)


算法思路:解法(暴搜 -> 记忆化搜索 -> 动态规划):
暴搜:

a. 递归含义:给 dfs 一个使命,给他一个数 i,返回以 i 位置为起点的最长递增子序列的长度;

b. 函数体:遍历 i 后面的所有位置,看看谁能加到 i 这个元素的后面.统计所有情况下的最大值.

c. 递归出口:因为我们是判断之后再进入递归的,因此没有出口~

记忆化搜索:

a. 加上一个备忘录;

b. 每次进入递归的时候,去备忘录里面看看;

c. 每次返回的时候,将结果加入到备忘录里面.

动态规划:

a. 递归含义 -> 状态表示;

b. 函数体 -> 状态转移方程;

c. 递归出口 -> 初始化.

核心代码

cpp 复制代码
class Solution
{
public:
    //主函数:入口,传入数组nums,返回最长递增子序列长度
    int lengthOfLIS(vector<int>& nums)
    {
        //方法1:动态规划(迭代/自底向上)
        //获取数组的长度
        int n = nums.size();
        //dp[i] 表示:以 nums[i] 为起点的最长递增子序列的长度
        //初始化:每个元素自身就是一个长度为1的子序列,所以全部赋值为1
        vector<int> dp(n, 1);
        //记录最终的答案(最长长度)
        int ret = 0;

        //填表顺序:从数组最后一个元素往前遍历(自底向上)
        for(int i = n - 1; i >= 0; i--)
        {
            //遍历 i 后面的所有元素 j
            for(int j = i + 1; j < n; j++)
            {
                //核心条件:nums[j] > nums[i],满足严格递增
                if(nums[j] > nums[i])
                {
                    //状态转移方程:
                    //dp[i] = max(自身原值, 以j为起点的最长长度 + 1)
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            //更新全局最大值,记录最长的子序列长度
            ret = max(ret, dp[i]);
        }
        //返回最终结果
        return ret;

        //方法2:记忆化搜索(递归/自顶向下)
        //备忘录数组:存储已经计算过的结果,避免重复递归
        //vector<int> memo(n);
        //int ret = 0;
        //遍历数组每个位置,计算以该位置为起点的最长长度,取最大值
        //for(int i = 0; i < n; i++)
        // ret = max(ret, dfs(i, nums, memo));
        //return ret;
    }

    //递归函数:记忆化搜索,计算以 pos 位置为起点的最长递增子序列长度
    //pos:当前遍历的数组下标
    //nums:原数组(引用传递)
    //memo:备忘录数组(引用传递,缓存结果)
    int dfs(int pos, vector<int>& nums, vector<int>& memo)
    {
        //第一步:查备忘录!如果当前位置已经计算过,直接返回结果(剪枝优化)
        if(memo[pos] != 0) 
            return memo[pos];

        //初始化:当前元素自身长度为1
        int ret = 1;
        //遍历 pos 后面的所有元素
        for(int i = pos + 1; i < nums.size(); i++)
        {
            //满足严格递增,才能接在当前元素后面
            if(nums[i] > nums[pos])
            {
                //递归计算后续长度,更新最大值
                ret = max(ret, dfs(i, nums, memo) + 1);
            }
        }

        //第二步:存备忘录!将计算结果存入,方便后续直接调用
        memo[pos] = ret;
        //返回以pos为起点的最长递增子序列长度
        return ret;
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Solution
{
public:
    // 动态规划
    int lengthOfLIS(vector<int>& nums)
    {
        int n = nums.size();
        if (n == 0) return 0;

        vector<int> dp(n, 1);
        int ret = 0;

        // 填表顺序:从后往前
        for (int i = n - 1; i >= 0; i--)
        {
            for (int j = i + 1; j < n; j++)
            {
                if (nums[j] > nums[i])
                {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            ret = max(ret, dp[i]);
        }

        return ret;
    }

    // 记忆化搜索入口
    int lengthOfLISMemo(vector<int>& nums)
    {
        int n = nums.size();
        if (n == 0) return 0;

        vector<int> memo(n, 0);
        int ret = 0;

        for (int i = 0; i < n; i++)
        {
            ret = max(ret, dfs(i, nums, memo));
        }

        return ret;
    }

    int dfs(int pos, vector<int>& nums, vector<int>& memo)
    {
        if (memo[pos] != 0) return memo[pos];

        int ret = 1;
        for (int i = pos + 1; i < nums.size(); i++)
        {
            if (nums[i] > nums[pos])
            {
                ret = max(ret, dfs(i, nums, memo) + 1);
            }
        }

        memo[pos] = ret;
        return ret;
    }
};

int main()
{
    Solution s;

    int n;
    cout << "请输入数组长度 n: ";
    cin >> n;

    if (n < 0)
    {
        cout << "数组长度不能为负数。" << endl;
        return 0;
    }

    vector<int> nums(n);
    cout << "请输入 " << n << " 个整数: ";
    for (int i = 0; i < n; i++)
    {
        cin >> nums[i];
    }

    int ans1 = s.lengthOfLIS(nums);
    int ans2 = s.lengthOfLISMemo(nums);

    cout << "动态规划结果: " << ans1 << endl;
    cout << "记忆化搜索结果: " << ans2 << endl;

    return 0;
}

5.猜数字大小||(OJ题)


算法思路:解法(暴搜 -> 记忆化搜索):
暴搜:

a. 递归含义:给 dfs 一个使命,给他一个区间 [left, right],返回在这个区间上能完胜的最小费用;

b. 函数体:选择 [left, right] 区间上的任意一个数作为头结点,然后递归分析左右子树.求出所有情况下的最小值;

c. 递归出口:当 left >= right 的时候,直接返回 0.

记忆化搜索:

a. 加上一个备忘录;

b. 每次进入递归的时候,去备忘录里面看看;

c. 每次返回的时候,将结果加入到备忘录里面.

核心代码

cpp 复制代码
class Solution
{
    // 备忘录数组:memo[left][right] 存储区间 [left, right] 内确保获胜的最小现金数
    //题目 n 最大为 200,因此定义 201x201 覆盖所有区间
    int memo[201][201];

public:
    //主函数:入口,传入数字上限 n,返回最终结果
    int getMoneyAmount(int n)
    {
        //调用递归函数,计算区间 [1, n] 的最小获胜金额
        return dfs(1, n);
    }

    //递归函数:记忆化搜索
    //输入:区间 [left, right]
    //返回:在该区间内猜数字,确保获胜的最小现金数
    int dfs(int left, int right)
    {
        //递归出口1:区间无效(左 >= 右)
        //说明区间只有1个数或没有数,不需要花钱猜测,直接返回0
        if(left >= right) 
            return 0;
            
        //递归出口2:查备忘录
        //如果该区间的结果已经计算过,直接返回,避免重复递归(核心优化)
        if(memo[left][right] != 0) 
            return memo[left][right];

        //初始化结果为无穷大,用于后续找最小值
        int ret = INT_MAX;
        
        //遍历区间内的每一个数字 head,尝试将其作为本次猜测的数字
        for(int head = left; head <= right; head++)
        {
            //递归计算:选择 head 后,左区间 [left, head-1] 的最小花费
            int x = dfs(left, head - 1);
            //递归计算:选择 head 后,右区间 [head+1, right] 的最小花费
            int y = dfs(head + 1, right);
            
            //核心逻辑:
            //1.必须考虑最坏情况:max(x, y) → 保证能赢
            //2.加上本次猜测的花费:head
            //3.取所有猜测方案中的最小值:min(ret, ...)
            ret = min(ret, head + max(x, y));
        }
        
        //将计算好的结果存入备忘录,方便后续直接调用
        memo[left][right] = ret;
        
        //返回当前区间 [left, right] 的最小获胜金额
        return ret;
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
#include <cstring>
#include <climits>
using namespace std;

class Solution
{
    int memo[201][201];

public:
    int getMoneyAmount(int n)
    {
        memset(memo, 0, sizeof(memo)); // 初始化备忘录
        return dfs(1, n);
    }

    int dfs(int left, int right)
    {
        if (left >= right) return 0;
        if (memo[left][right] != 0) return memo[left][right];

        int ret = INT_MAX;

        for (int head = left; head <= right; head++) // 枚举当前猜的数字
        {
            int x = dfs(left, head - 1);
            int y = dfs(head + 1, right);
            ret = min(ret, head + max(x, y));
        }

        memo[left][right] = ret;
        return ret;
    }
};

int main()
{
    Solution s;

    int n;
    cout << "请输入 n: ";
    cin >> n;

    if (n <= 0 || n > 200)
    {
        cout << "n 的范围应为 1 ~ 200" << endl;
        return 0;
    }

    cout << "最少需要准备的钱数: " << s.getMoneyAmount(n) << endl;

    return 0;
}

6.矩阵中的最长递增路径(OJ题)


算法思路:解法(暴搜 -> 记忆化搜索):
暴搜:

a. 递归含义:给 dfs 一个使命,给它一个下标 [i, j],返回从这个位置开始的最长递增路径的长度;

b. 函数体:上下左右四个方向瞅一瞅,哪里能过去就过去,统计四个方向上的最大长度;

c. 递归出口:因为我们是先判断再进入递归,因此没有出口~

记忆化搜索:

a. 加上一个备忘录;

b. 每次进入递归的时候,去备忘录里面看看;

c. 每次返回的时候,将结果加入到备忘录里面.

核心代码

cpp 复制代码
class Solution
{
    //备忘录数组:memo[left][right] 存储区间 [left, right] 内确保获胜的最小现金数
    //题目 n 最大为 200,因此定义 201x201 覆盖所有区间
    int memo[201][201];

public:
    //主函数:入口,传入数字上限 n,返回最终结果
    int getMoneyAmount(int n)
    {
        //调用递归函数,计算区间 [1, n] 的最小获胜金额
        return dfs(1, n);
    }

    //递归函数:记忆化搜索
    //输入:区间 [left, right]
    //返回:在该区间内猜数字,确保获胜的最小现金数
    int dfs(int left, int right)
    {
        //递归出口1:区间无效(左 >= 右)
        //说明区间只有1个数或没有数,不需要花钱猜测,直接返回0
        if(left >= right) 
            return 0;
            
        //递归出口2:查备忘录
        //如果该区间的结果已经计算过,直接返回,避免重复递归(核心优化)
        if(memo[left][right] != 0) 
            return memo[left][right];

        //初始化结果为无穷大,用于后续找最小值
        int ret = INT_MAX;
        
        //遍历区间内的每一个数字 head,尝试将其作为本次猜测的数字
        for(int head = left; head <= right; head++)
        {
            //递归计算:选择 head 后,左区间 [left, head-1] 的最小花费
            int x = dfs(left, head - 1);
            //递归计算:选择 head 后,右区间 [head+1, right] 的最小花费
            int y = dfs(head + 1, right);
            
            //核心逻辑:
            //1.必须考虑最坏情况:max(x, y) → 保证能赢
            //2.加上本次猜测的花费:head
            //3.取所有猜测方案中的最小值:min(ret, ...)
            ret = min(ret, head + max(x, y));
        }
        
        //将计算好的结果存入备忘录,方便后续直接调用
        memo[left][right] = ret;
        
        //返回当前区间 [left, right] 的最小获胜金额
        return ret;
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

class Solution
{
    int m, n;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int memo[201][201];

public:
    int longestIncreasingPath(vector<vector<int>>& matrix)
    {
        if (matrix.empty() || matrix[0].empty()) return 0;

        memset(memo, 0, sizeof(memo));

        int ret = 0;
        m = matrix.size();
        n = matrix[0].size();

        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                ret = max(ret, dfs(matrix, i, j));
            }
        }
        return ret;
    }

    int dfs(vector<vector<int>>& matrix, int i, int j)
    {
        if (memo[i][j] != 0) return memo[i][j];

        int ret = 1;
        for (int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && matrix[x][y] > matrix[i][j])
            {
                ret = max(ret, dfs(matrix, x, y) + 1);
            }
        }

        memo[i][j] = ret;
        return ret;
    }
};

int main()
{
    int row, col;
    cout << "请输入矩阵的行数和列数: ";
    cin >> row >> col;

    if (row <= 0 || col <= 0 || row > 200 || col > 200)
    {
        cout << "行数和列数应在 1 ~ 200 之间。" << endl;
        return 0;
    }

    vector<vector<int>> matrix(row, vector<int>(col));

    cout << "请输入矩阵元素:" << endl;
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            cin >> matrix[i][j];
        }
    }

    Solution s;
    cout << "最长递增路径长度: " << s.longestIncreasingPath(matrix) << endl;

    return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!


敬请期待下一篇文章内容:递归、搜索与回溯算法的内容到这里就圆满结束啦!小编开始继续学习另外的算法领域,不断提高自己的算法能力,喜欢小编的可以继续跟着我的步伐一起继续前行!


每日心灵鸡汤:他为什么变得越来越牛?
后来这个男孩缓过劲来,他开始热爱生活他,开始相信自己,他不再害怕孤独,不再因别人的一句话而难过一整天,他学会了钝感力屏蔽力,他降低了期待,戒掉了敏感,断掉了所有无用的社交,接受了所有的世事无常,拒绝了所有让他内耗的事,他开始把一切都变得无所谓了起来,因为他明白他已经长大了,必须要对自己的人生负责了,所以他开始发了疯似的学习,开始狠狠的搞钱搞事业,开始满脑子都是自己的前途和梦想,开始自律,开始醒悟,开始杀掉那个懒惰又颓废的自己. 现在的他正在向好的方向发展,开始变得越来越厉害,越来越勇敢,他不是别人,他就是屏幕前的你,留下一句这就是我,我们山顶见!

相关推荐
REDcker1 小时前
跨平台编译详解 工具链配置与工程化实践
linux·c++·windows·macos·c·跨平台·编译
闻缺陷则喜何志丹1 小时前
【 线性筛 调和级数】P7281 [COCI 2020/2021 #4] Vepar|普及+
c++·算法·洛谷·线性筛·调和级数
zzzsde1 小时前
【Linux】线程概念与控制(1)线程基础与分页式存储管理
linux·运维·服务器·开发语言·算法
穿条秋裤到处跑1 小时前
每日一道leetcode(2026.04.23):等值距离和
算法·leetcode·职场和发展
少许极端2 小时前
算法奇妙屋(四十九)-贡献法
java·算法·leetcode·贡献法
叶子野格2 小时前
《C语言学习:数组》11
c语言·开发语言·c++·学习·visual studio
武帝为此2 小时前
【特征选择方法】
算法·数学建模
Little At Air2 小时前
C++priority_queue模拟实现
开发语言·数据结构·c++
程序员zgh2 小时前
C++ decltype 关键字 详解
c语言·开发语言·c++