剑指offer经典题目整理(二)

一、斐波那契数列(fib)

1.链接

斐波那契数列_牛客题霸_牛客网 (nowcoder.com)

2.描述

斐波那契数列就是数列中任意一项数字,都会等于前两项之和,满足f(n) = f(n-1) + f(n-2) 的一个数列,例如:1 1 2 3 5 8 13 21 ...

现在题目规定第一项和第二项为1,输入一个整数n,代表第n项,要求返回第n项的结果

3.思路

思路一:动态规划

斐波那契数列是动态规划中最经典简单的一个代表,其中的公式就是动态规划的状态方程,这题也是用来引入简单动态规划问题的一个经典题目

解决动态规划类的问题,需要三步:

第一步是设置状态参数n,本题中的状态参数n代表的就是斐波那契数列的第n项

第二步是根据题目意思去寻找关于n的状态方程,本题的状态方程就是f(n) = f(n-1) + f(n-2)

第三步是确定初始状态,本题中的初始状态题目给出,f(1) = 1 , f(2) = 1

确定好这三步后就可以根据状态方程和初始值去写代码了

思路二:递归与剪枝

第二种解决的思路,其实是分治思想,利用递归的方式,要想知道f(n),就需要知道f(n-1)和f(n-2) 的结果,要知道f(n-1),就要先知道f(n-2)和f(n-3)... 不断向下递归,直到递归到初始值,就可以返回得到结果,但是这种递归会存在大量的重复计算,由于递归会形成新的栈帧,对内存和计算效率的成本都非常大,因此,通过剪枝的方式去减少重复运算,这里的剪枝,就是指,可以将递归过程中,已经计算好的结果,存入到一个容器中,避免重复运算,这里我选择用map容器去实现剪枝操作

4.参考代码

思路一:动态规划

cpp 复制代码
class Solution 
{
public:
    int Fibonacci(int n) 
    {
        if(n == 1 || n == 2)
        {
            return 1;
        }
        vector<int> dp;
        dp.reserve(n+1);
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 1;
        for(int i = 3; i<=n;i++)
        {
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
};

代码分析

思路二:递归与剪枝

cpp 复制代码
class Solution {
  public:

    int Fibonacci_recursion(int n, map<int, int>& fib) {
        if (n == 1 || n == 2) 
        {
            return 1;
        }
        int pre = 0;
        if (fib.find(n - 1) == fib.end())  //没有记录过
        {
            pre = Fibonacci_recursion(n - 1, fib);
            fib[n - 1] = pre;
        } 
        else 
        {
            pre = fib[n - 1];
        }
        int ppre = 0;
        if (fib.find(n - 2) == fib.end()) 
        {
            ppre = Fibonacci_recursion(n - 2, fib);
            fib[n - 2] = ppre;
        } 
        else 
        {
            ppre = fib[n - 2];
        }

        return pre+ppre;
    }

    int Fibonacci(int n) {
        map<int, int> fib;
        return Fibonacci_recursion(n, fib);
    }
};

二、青蛙跳台阶

1.链接

跳台阶_牛客题霸_牛客网 (nowcoder.com)

2.描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

3.思路

青蛙跳台阶问题,其实就是斐波那契数列的一个变形,也是动态规划的思路去解决,同样是三步

第一步状态定义:设跳到第n台阶一共有f(n)种跳法

第二步状态方程:通过分析,可以发现,第n台阶的跳法,跳到第n阶前,要么在n-1个台阶,要么在n-2个台阶上,第n阶跳法总数会等于n-1台阶的跳法总数+n-2台阶的跳法总数,所以状态方程也就是f(n) = f(n-1) + f(n-2)

第三步初始状态:f(1) = 1,f(2) = 2

4.参考代码

cpp 复制代码
class Solution {
public:
    int jumpFloor(int number) 
    {
        vector<int> dp;
        dp.reserve(number+1);
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3;i<=number;i++)
        {
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[number];
    }
};

三、矩形覆盖

1.链接

矩形覆盖_牛客题霸_牛客网 (nowcoder.com)

2.描述

我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,从同一个方向看总共有多少种不同的方法?

3.思路

核心还是斐波那契数列的变形,也是采用动态规划的思路

第一状态定义:用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,从同一个方向看总共有f(n)种不同的方法

第二状态方程:每次放方块,实际只有两种放置方式,一种是竖着放(放一个),一种是横着放(放两个),类比与青蛙跳台阶一样的思路,最后放置完n个的状态,要么采用竖着放(剩下一个),要么横着放(剩下两个),而放完n个小方块的方法总数就会是这两种状态的方法总数之和

即,f(n) = f(n-1) + f(n-2)

第三初始参数:f(1)= 1 , f(2) = 2

4.参考代码

cpp 复制代码
class Solution {
  public:
    int rectCover(int number) {
        vector<int> dp;
        dp.reserve(number + 1);
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= number; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[number];
    }
};

总结

本次总结的内容是关于剑指offer中,一些关于斐波那契数列以及其变形的经典题目,也是简单的动态规划题目,题目虽然简单,但动态规划的思想比较难,这里算是一个简单的引入,之后还会更加深入的学习动态规划

相关推荐
ideaout技术团队43 分钟前
leetcode学习笔记2:多数元素(摩尔投票算法)
学习·算法·leetcode
代码充电宝1 小时前
LeetCode 算法题【简单】283. 移动零
java·算法·leetcode·职场和发展
不枯石3 小时前
Matlab通过GUI实现点云的均值滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab·均值算法
不枯石3 小时前
Matlab通过GUI实现点云的双边(Bilateral)滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab
白水先森5 小时前
C语言作用域与数组详解
java·数据结构·算法
想唱rap6 小时前
直接选择排序、堆排序、冒泡排序
c语言·数据结构·笔记·算法·新浪微博
老葱头蒸鸡7 小时前
(27)APS.NET Core8.0 堆栈原理通俗理解
算法
视睿7 小时前
【C++练习】06.输出100以内的所有素数
开发语言·c++·算法·机器人·无人机
保持低旋律节奏7 小时前
CPP——OJ试题,string、vector、类(题三)初步应用
c++
君生我老8 小时前
C++ string类常用操作
c++