【四十三】【算法分析与设计】记忆化递归(1),509. 斐波那契数,62. 不同路径,300. 最长递增子序列

目录

[509. 斐波那契数](#509. 斐波那契数)

暴力递归

记忆化递归

动态规划

[62. 不同路径](#62. 不同路径)

暴力递归

记忆化递归

动态规划

[300. 最长递增子序列](#300. 最长递增子序列)

暴力递归

记忆化递归

动态规划

结尾


509. 斐波那契数

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1

F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n)

示例 1:

输入: n = 2 输出: 1 **解释:**F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入: n = 3 输出: 2 **解释:**F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入: n = 4 输出: 3 **解释:**F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

  • 0 <= n <= 30

暴力递归

复制代码
cpp 复制代码
class Solution {
public:
    int fib(int n) {
        return dfs(n);
    }

    int dfs(int n){
        if(n==0||n==1) return n;

        return dfs(n-1)+dfs(n-2);
    }
};

记忆化递归

复制代码
cpp 复制代码
class Solution {
public:
    int memory[31]; // 定义一个数组用于存储已经计算过的斐波那契数,以便重用,提高效率。数组大小为31,假设我们不会计算超过30的斐波那契数。

    int fib(int n) { // 定义一个公有函数fib,接收一个整数n,返回计算得到的斐波那契数。
        return dfs(n); // 调用私有的递归函数dfs来实际计算斐波那契数,并返回其结果。
    }

    int dfs(int n){ // 定义一个私有的递归函数,用于实际计算斐波那契数。
        if(n==0||n==1) return n; // 递归的基础情况:如果n是0或1,直接返回n(因为斐波那契序列的前两项分别是0和1)。

        if(memory[n]!=0) return memory[n]; // 如果当前斐波那契数已经被计算过(即memory[n]不为0),直接返回存储的结果,避免重复计算。

        memory[n]=dfs(n-1)+dfs(n-2); // 计算当前斐波那契数,通过递归调用dfs函数计算前两个斐波那契数之和,并存储结果在memory数组中。

        return memory[n]; // 返回当前斐波那契数的计算结果。
    }
};

动态规划

复制代码
cpp 复制代码
class Solution {
public:
    int fib(int n) { // 定义一个公有成员函数fib,接受一个整数n,返回斐波那契数列的第n项。
        int dp[31]; // 定义一个数组dp用于存储斐波那契数列的值,数组大小为31,考虑到函数输入的范围。
        dp[0] = 0, dp[1] = 1; // 初始化斐波那契数列的前两项,dp[0]为第0项,dp[1]为第1项。

        for(int i = 2; i <= n; i++) { // 从第2项开始计算斐波那契数列的每一项,直到第n项。
            dp[i] = dp[i-1] + dp[i-2]; // 每一项是前两项的和。
        }
        return dp[n]; // 返回斐波那契数列的第n项。
    }
};

62. 不同路径

一个机器人位于一个 m x n网格的左上角 (起始点在下图中标记为 "Start" )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 "Finish" )。

问总共有多少条不同的路径?

示例 1:

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

示例 2:

  1. 输入: m = 3, n = 2 输出: 3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 向右 -> 向下 -> 向下 向下 -> 向下 -> 向右 向下 -> 向右 -> 向下

示例 3:

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

示例 4:

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

提示:

  • 1 <= m, n <= 100

  • 题目数据保证答案小于等于 2 * 10(9)

暴力递归

复制代码
cpp 复制代码
class Solution {
public:
    int uniquePaths(int m, int n) {
        return dfs(m,n);
    }

    int dfs(int i,int j){
        if(i==0||j==0) return 0;
        if(i==1&&j==1) return 1;

        return dfs(i-1,j)+dfs(i,j-1);
    }
};

记忆化递归

复制代码
cpp 复制代码
class Solution {
public:
    int memory[101][101]; // 定义一个二维数组memory用于存储已经计算过的路径数量,减少重复计算。数组的大小设为101x101,考虑到题目可能的m和n的最大值。

    int uniquePaths(int m, int n) { // 定义一个公有函数uniquePaths,接收两个整数m和n,返回从网格的左上角到右下角的唯一路径数量。
        return dfs(m, n); // 调用私有的递归函数dfs来计算路径数量,并返回结果。
    }

    int dfs(int i, int j){ // 定义一个私有的递归函数dfs,用于实际计算路径数量。i和j表示当前位置在网格中的坐标(从1开始计数)。
        if(i == 0 || j == 0) return 0; // 如果i或j为0,则超出网格边界,按照题目设定,这样的路径不合法,返回0。

        if(i == 1 && j == 1) return 1; // 如果i和j都为1,表示到达了起点,按照定义这是一条有效路径,返回1。

        if(memory[i][j] != 0) return memory[i][j]; // 如果这个位置的路径数量已经被计算过(即memory[i][j]不为0),直接返回存储的结果,避免重复计算。

        memory[i][j] = dfs(i - 1, j) + dfs(i, j - 1); // 计算当前位置的路径数量。路径可以从左边来(即dfs(i - 1, j))或从上面来(即dfs(i, j - 1)),将这两部分的路径数相加得到当前位置的总路径数,并存储在memory数组中。

        return memory[i][j]; // 返回当前位置的路径数量。
    }
};

动态规划

复制代码
cpp 复制代码
class Solution {
public:
    int uniquePaths(int m, int n) { // 定义一个公有函数uniquePaths,接收两个整数m和n,分别代表网格的行数和列数,返回从网格的左上角到右下角的唯一路径数量。
        int dp[101][101]; // 定义一个二维数组dp,用于存储到达每个格子的路径数量。
        for(int i=0; i<m; i++)
            dp[i][0] = 0; // 初始化第一列的所有格子的路径数量为0。
        for(int j=0; j<n; j++)
            dp[0][j] = 0; // 初始化第一行的所有格子的路径数量为0。

        dp[1][1] = 1; // 将起点(左上角)的路径数量初始化为1,因为从起点到起点自身有一条路径。

        for(int i = 1; i <= m; i++){ // 从第一行开始,遍历网格的每一行。
            for(int j = 1; j <= n; j++){ // 从第一列开始,遍历网格的每一列。
                if(i == 1 && j == 1) continue; // 如果是起点,则跳过当前循环,因为起点的路径数量已经初始化为1。
                dp[i][j] = dp[i-1][j] + dp[i][j-1]; // 计算到达当前格子的路径数量。路径可以从左边的格子来,也可以从上面的格子来,所以当前格子的路径数量是这两个格子的路径数量之和。
            }
        }
        return dp[m][n]; // 返回到达网格右下角的路径数量。
    }
};

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7]

子序列

示例 1:

输入: nums = [10,9,2,5,3,7,101,18] 输出: 4 **解释:**最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入: nums = [0,1,0,3,2,3] **输出:**4

示例 3:

输入: nums = [7,7,7,7,7,7,7] **输出:**1

提示:

  • 1 <= nums.length <= 2500

  • -10(4) <= nums[i] <= 10(4)

进阶:

  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

暴力递归

复制代码
cpp 复制代码
class Solution {
public:
    vector<int> nums;
    int lengthOfLIS(vector<int>& _nums) {
        int n = _nums.size();
        nums = _nums;
        // 定义dfs(pos) 表示以pos为起点,最长的递增子序列
        int ret = 0;
        for (int i = 0; i < n; i++) {
            ret = max(ret, dfs(i));
        }
        return ret;
    }

    int dfs(int pos) {

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

记忆化递归

复制代码
cpp 复制代码
class Solution {
public:
    vector<int> nums; // 存储传入的数组
    int memory[2501]; // 定义一个记忆化数组,用于存储计算过的结果,避免重复计算。数组大小为2501,考虑到题目中数组大小的可能范围。

    int lengthOfLIS(vector<int>& _nums) { // 公有函数,接收一个整数数组引用,返回该数组的最长递增子序列的长度。
        int n = _nums.size(); // 获取数组的大小。
        nums = _nums; // 将传入的数组赋值给类的成员变量。
        // 定义dfs(pos)表示以pos为起点,最长的递增子序列的长度。
        int ret = 0; // 初始化最长递增子序列的长度为0。
        for (int i = 0; i < n; i++) { // 遍历数组的每个元素,以每个元素为起点,寻找最长递增子序列。
            ret = max(ret, dfs(i)); // 更新最长递增子序列的长度。
        }
        return ret; // 返回最长递增子序列的长度。
    }

    int dfs(int pos) { // 定义一个私有的递归函数,用于计算以pos为起点的最长递增子序列的长度。
        if (memory[pos] != 0)
            return memory[pos]; // 如果当前位置的结果已经计算过,则直接返回记忆化的结果,避免重复计算。
        int ret = 0; // 初始化以当前元素后面符合要求的元素开始的最长递增子序列的长度为0。
        for (int i = pos + 1; i < nums.size(); i++) { // 遍历当前位置之后的所有元素。
            if (nums[i] > nums[pos]) { // 如果找到一个比当前元素大的元素,说明可以形成递增序列。
                ret = max(ret, dfs(i)); // 更新最长递增子序列的长度。
            }
        }
        memory[pos] = ret + 1; // 在当前位置的递增子序列长度中加1(加上当前位置的元素),并存储在记忆化数组中。
        return ret + 1; // 返回以当前位置为起点的最长递增子序列的长度。
    }
};

动态规划

复制代码
cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) { // 定义一个公有成员函数,接受一个整数数组的引用,返回这个数组的最长递增子序列的长度。
        int dp[2501]; // 定义一个动态规划数组dp,用于存储以每个元素开始的最长递增子序列的长度。
        int n = nums.size(); // 获取输入数组的大小。
        int ret = 0; // 初始化最终结果为0,即最长递增子序列的长度。

        for (int i = n - 1; i >= 0; i--) { // 从数组的最后一个元素向前遍历。
            int ret1 = 0; // 初始化以当前元素后面符合要求的元素开始的最长递增子序列的长度为0。
            for (int j = i + 1; j < n; j++) { // 遍历当前元素之后的所有元素。
                if (nums[j] > nums[i]) // 如果后面的元素大于当前元素,说明可以形成递增关系。
                    ret1 = max(ret1, dp[j]); // 更新以当前元素后面符合要求的元素开始的最长递增子序列的长度,为后面所有可以形成递增关系的元素对应的最长递增子序列长度的最大值。
            }
            dp[i] = ret1 + 1; // 在最长递增子序列长度上加1(包括当前元素自身),并将结果存储在dp数组中。
            ret = max(ret, dp[i]); // 更新全局的最长递增子序列的长度。
        }

        return ret; // 返回最长递增子序列的长度。
    }
};

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

相关推荐
霁月风31 分钟前
设计模式——适配器模式
c++·适配器模式
sp_fyf_202432 分钟前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
ChoSeitaku1 小时前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
偷心编程1 小时前
双向链表专题
数据结构
香菜大丸1 小时前
链表的归并排序
数据结构·算法·链表
jrrz08281 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time1 小时前
golang学习2
算法
咖啡里的茶i1 小时前
Vehicle友元Date多态Sedan和Truck
c++
海绵波波1071 小时前
Webserver(4.9)本地套接字的通信
c++
@小博的博客1 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习