Leetcode70——爬楼梯(斐波那契类型)(C语言)(通过该问题讲解动态规划基本思想)

本文主要是介绍Leetcode70——爬楼梯(斐波那契类型)(C语言)(通过该问题讲解动态规划基本思想),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

原题链接:        

动态规划的基本思想:

爬楼梯题目讲解:

解法1(递归):

解法2(记忆化递归):

解法3(Fibonacci数列的动态规划算法):

总结:


原题链接:        

原题链接:70. 爬楼梯 - 力扣(LeetCode)


动态规划的基本思想:

        动态规划(Dynamic Programming,简称DP)是一种算法设计技术,它通过将复杂问题分解为更小的子问题来解决优化问题。动态规划通常用于解决具有重叠子问题和最优子结构特性的问题。
        动态规划的基本思路可以概括为以下几个步骤:
                1. 定义状态:首先,需要定义问题中的状态。状态是指在特定步骤或阶段的问题的某种描述。通常,状态可以用一个或多个变量来表示,并且状态之间存在依赖关系。
                2. 状态转移方程:其次,确定状态转移方程,也称为递推关系。这个方程描述了状态如何从之前的某个状态或多个状态转移而来。在形式上,这个方程通常表示为 `dp[i] = f(dp[i-1], dp[i-2], ...)`,其中 `dp[i]` 是到达第 `i` 个状态的方法数或成本,而 `f` 是一个关于前状态的函数。
                3. 边界条件:确定初始状态或边界条件。这些是问题的基础情况,通常可以直接计算得到,或者由问题的性质直接给出。
                4. 计算顺序:确定状态的计算顺序。通常,状态的计算顺序应该能够充分利用之前计算的结果,避免重复计算。
                5. 构建DP表:根据状态转移方程和边界条件,构建一个DP表(或数组),并按顺序填充表中的每个状态。
                6. 输出结果:最后,根据DP表中的最终状态输出结果。


        动态规划的关键优点是它能够显著减少计算复杂度,将指数级问题转化为多项式级问题。这主要是因为动态规划避免了重复计算相同的状态,而是利用已计算的状态结果。


        动态规划适用于具有最优子结构特性的问题,即一个问题的最优解包含了其子问题的最优解。此外,动态规划也适用于具有重叠子问题的问题,即在求解问题的过程中,相同的子问题会被多次计算。通过存储这些子问题的解,动态规划可以有效地减少计算量。


爬楼梯题目讲解:

        Fibonacci(斐波那契)数列问题,它是一个简单而典型的分治问题,Fibonacci数列的递归方程表示为:

        F(n) = F(n-1) + F(n-2);

        其中,F(n) 表示数列的第n项,F(n-1) 表示数列的第n-1项,F(n-2) 表示数列的第n-2项。

        注意,这个递归方程是从第三项开始的,因为前两项有明确的定义:

        F(0) = 0 ;

        F(1) = 1。

解法1(递归):

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>
#include <stdlib.h>
int climbStairs(int n) {if (n < 0) return 0;if (n == 1) return 1;if (n == 2) return 2;return climbStairs(n - 1) + climbStairs(n - 2);
}
int main() {int n;printf("输入台阶数>");scanf("%d", &n);printf("有%d种方法可以爬到楼顶。", climbStairs(n));return 0;
}

        该程序实现简单,但非常低效。以求解该题目不难发现,在递归调用过程中,每次产生的子问题并不是新问题,有些子问题被反复计算多次,比如climbStairs(3)调用了两次,climbStairs(2)调用了三次,这种现象被称为 重叠子问题。

        当输入参数n较大时,其重复计算的子问题数目将爆照式增长,最终需要计算的子问题个数(或者递归调用次数)将增长为指数级。



那么该如何减少实际求解的子问题的数目呢?

        如果用一个数组保存已解决的子问题的答案,而在需要时再从该数组中查找已经求解的子问题的答案,这样就可以避免大量的重复计算,从而得到多项式复杂性算法。这就是动态规划的基本思路。


        记忆化(缓存):由于递归会多次计算相同的子问题,这会导致大量的重复计算,特别是在 n 较大时。为了避免重复计算,我们可以使用一个数组 arr 来存储已经计算过的结果。如果 arr[n] 已经被计算过,就直接返回它的值,否则就计算它并存储起来。

解法2(记忆化递归):

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>
#include <stdlib.h>int arr[10];int climbStairs(int n) {int tmp;if (n < 0) {return 0;}else if (arr[n] != 0) {return arr[n];}else {tmp = climbStairs(n - 1) + climbStairs(n - 2);return tmp;}
}int main() {int n, i;for (i = 0; i < 50; i++) {arr[i] = 0;}arr[1] = 1;arr[2] = 2;printf("输入台阶数>");scanf("%d", &n);printf("有%d种方法可以爬到楼顶。", climbStairs(n));return 0;
}

解法3(Fibonacci数列的动态规划算法):

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>
#include <malloc.h>// 动态规划解决爬楼梯问题
int climbStairs(int n) {if (n <= 2) {return n;}int* dp = (int*)malloc((n + 1) * sizeof(int));dp[1] = 1;dp[2] = 2;for (int i = 3; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}int result = dp[n];free(dp); // 释放分配的内存return result;
}int main() {int n;printf("输入台阶数: ");scanf("%d", &n);printf("有 %d 种方法可以爬到楼顶。\n", climbStairs(n));return 0;
}

        该代码基本思路是:要到达第 `n` 阶,你可以从第 `n-1` 阶爬 1 阶上来,或者从第 `n-2` 阶爬 2 阶上来。因此,到达第 `n` 阶的方法数等于到达第 `n-1` 阶和第 `n-2` 阶方法数的和。

动态规划的步骤如下:
        1. 初始化:首先,我们需要定义一个数组 `dp` 来存储到达每一阶楼梯的方法数。由于到达第一阶和第二阶的方法数是固定的,我们可以直接初始化 `dp[1] = 1` 和 `dp[2] = 2`。
        2. 递推计算:然后,我们从第三阶开始,逐个计算到达每一阶的方法数。对于每一阶 `i`(`i` 从 3 开始到 `n`),它的方法数 `dp[i]` 等于前两阶方法数的和,即 `dp[i] = dp[i-1] + dp[i-2]`。
        3. 输出结果:最后,当计算完所有阶数后,`dp[n]` 就是到达楼顶的方法数。我们将这个值输出即可。
        在代码中,我们使用了一个循环来逐阶计算方法数,并在循环结束后返回 `dp[n]` 的值。在主函数中,我们接收用户输入的楼梯阶数 `n`,并调用 `climbStairs` 函数来计算并输出结果。
        注意,我们使用了 `malloc` 来动态分配内存,这是因为我们在编译时不知道 `n` 的值,因此不能使用静态数组。在使用完动态分配的内存后,我们使用 `free` 函数来释放内存,这是一个良好的编程习惯,可以防止内存泄露。


总结:

        Fibonacci数列的例子可以得到,动态规划的关键在于解决重叠子问题的重复计算,将原来指数级复杂度的分治算法改进多项式级的计算。

        在实现过程中,动态规划算法需要存储各子问题的解,所以它的空间复杂度大于其他算法,这是一种空间换时间的策略。

这篇关于Leetcode70——爬楼梯(斐波那契类型)(C语言)(通过该问题讲解动态规划基本思想)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/859697

相关文章

关于@MapperScan和@ComponentScan的使用问题

《关于@MapperScan和@ComponentScan的使用问题》文章介绍了在使用`@MapperScan`和`@ComponentScan`时可能会遇到的包扫描冲突问题,并提供了解决方法,同时,... 目录@MapperScan和@ComponentScan的使用问题报错如下原因解决办法课外拓展总结@

MybatisGenerator文件生成不出对应文件的问题

《MybatisGenerator文件生成不出对应文件的问题》本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成... 目录MyBATisGenerator 文件生成不出对应文件先在项目结构里引入“targetProje

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

IDEA如何将String类型转json格式

《IDEA如何将String类型转json格式》在Java中,字符串字面量中的转义字符会被自动转换,但通过网络获取的字符串可能不会自动转换,为了解决IDEA无法识别JSON字符串的问题,可以在本地对字... 目录问题描述问题原因解决方案总结问题描述最近做项目需要使用Ai生成json,可生成String类型

Go语言实现将中文转化为拼音功能

《Go语言实现将中文转化为拼音功能》这篇文章主要为大家详细介绍了Go语言中如何实现将中文转化为拼音功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 有这么一个需求:新用户入职 创建一系列账号比较麻烦,打算通过接口传入姓名进行初始化。想把姓名转化成拼音。因为有些账号即需要中文也需要英

numpy求解线性代数相关问题

《numpy求解线性代数相关问题》本文主要介绍了numpy求解线性代数相关问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 在numpy中有numpy.array类型和numpy.mat类型,前者是数组类型,后者是矩阵类型。数组

使用Python进行文件读写操作的基本方法

《使用Python进行文件读写操作的基本方法》今天的内容来介绍Python中进行文件读写操作的方法,这在学习Python时是必不可少的技术点,希望可以帮助到正在学习python的小伙伴,以下是Pyth... 目录一、文件读取:二、文件写入:三、文件追加:四、文件读写的二进制模式:五、使用 json 模块读写

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas