《LeetCode 300 最长递增子序列 记忆化搜索DFS解法》

一.题目

300. 最长递增子序列 - 力扣(LeetCode)

二.思路讲解

2.1 思路讲解

本题要求最长递增子序列的长度,可以采用递归 + 记忆化搜索的经典方式。

  • 递归定义 :设 dfs(pos) 表示从下标 pos 开始(必须包含 nums[pos])的最长递增子序列的长度。那么它等于 1(自身)加上所有满足 nums[i] > nums[pos] 的位置 idfs(i) 的最大值

  • 递归基 :对于最后一个元素,dfs(pos) = 1

  • 记忆化优化 :直接递归会导致大量重复计算,因此引入一个备忘录数组 memo,在计算 dfs(pos) 前先查表,若已计算则直接返回,否则递归计算后存入备忘录。

  • 最终答案 :遍历所有位置作为起点,取 dfs(pos) 的最大值。

三.代码演示

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        vector<int> memo(nums.size());
        int ret = 0;
        for(int i = 0;i < nums.size();i++)
        {
            ret = max(ret,dfs(nums,i,memo));
        }
        return ret;
    }
    int dfs(vector<int>& nums,int pos,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(nums,i,memo) + 1);
            }
        }
        memo[pos] = ret;//跑到尽头了
        return memo[pos];

    }
};

四.代码讲解

一、备忘录设计

为了消除重复计算,我们使用一个一维数组 memo,其长度与 nums 相同,memo[pos] 表示以 nums[pos] 作为子序列起点(必须包含该元素)的最长严格递增子序列的长度。初始值均为 0,表示尚未计算。

二、主函数 lengthOfLIS
  1. 创建一个大小为 nums.size()memo 数组,并初始化为 0

  2. 遍历所有可能的下标 i,计算以 nums[i] 开头的最长递增子序列长度 dfs(nums, i, memo),并更新全局最大值 ret

  3. 返回 ret

三、递归函数 dfs

dfs(nums, pos, memo) 返回以 nums[pos] 开头的最长递增子序列的长度。执行流程如下:

1. 查备忘录

如果 memo[pos] != 0,说明该子问题已经计算过,直接返回存储的值。这是记忆化搜索的核心优化。

2. 初始化当前结果

ret 初始化为 1,因为子序列至少包含自身。

3. 向后枚举可能的后续元素

i = pos + 1 开始遍历到数组末尾,如果 nums[i] > nums[pos],说明 i 可以接在 pos 之后构成更长的递增子序列。此时递归计算以 i 开头的子序列长度,即 dfs(nums, i, memo) + 1,并用 max 更新 ret

4. 存入备忘录并返回

将计算得到的 ret 存入 memo[pos],然后返回 ret

四、关键细节
  • 递归定义dfs(pos) 必须包含 nums[pos],因此递归基隐含在循环中:当 pos 为最后一个元素时,循环不执行,ret 保持为 1

  • 记忆化作用 :每个下标 pos 只计算一次,之后直接查表返回,避免了大量重复递归。原本的暴力递归时间复杂度为 O(2^n) ,优化后降为 O(n²)

五、流程图