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

相关文章

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

Python Faker库基本用法详解

《PythonFaker库基本用法详解》Faker是一个非常强大的库,适用于生成各种类型的伪随机数据,可以帮助开发者在测试、数据生成、或其他需要随机数据的场景中提高效率,本文给大家介绍PythonF... 目录安装基本用法主要功能示例代码语言和地区生成多条假数据自定义字段小结Faker 是一个 python

如何解决mmcv无法安装或安装之后报错问题

《如何解决mmcv无法安装或安装之后报错问题》:本文主要介绍如何解决mmcv无法安装或安装之后报错问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mmcv无法安装或安装之后报错问题1.当我们运行YOwww.chinasem.cnLO时遇到2.找到下图所示这里3.

浅谈配置MMCV环境,解决报错,版本不匹配问题

《浅谈配置MMCV环境,解决报错,版本不匹配问题》:本文主要介绍浅谈配置MMCV环境,解决报错,版本不匹配问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录配置MMCV环境,解决报错,版本不匹配错误示例正确示例总结配置MMCV环境,解决报错,版本不匹配在col

Pydantic中Optional 和Union类型的使用

《Pydantic中Optional和Union类型的使用》本文主要介绍了Pydantic中Optional和Union类型的使用,这两者在处理可选字段和多类型字段时尤为重要,文中通过示例代码介绍的... 目录简介Optional 类型Union 类型Optional 和 Union 的组合总结简介Pyd

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

Python基础文件操作方法超详细讲解(详解版)

《Python基础文件操作方法超详细讲解(详解版)》文件就是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位,文件的核心操作就是读和写,:本文主要介绍Python基础文件操作方法超详细讲解的相... 目录一、文件操作1. 文件打开与关闭1.1 打开文件1.2 关闭文件2. 访问模式及说明二、文件读写1.