Bootstrap

动态规划斐波那契类型整理

力扣509斐波那契数

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

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

题目分析

利用斐波那契数列求第n个数

解题思路

最简单的动态规划问题,递推公式以及初始化已给,直接动态规划解决

代码实现

class Solution {
public:
    int fib(int n) {
        if(n<=1)return n;
        vector <int>dp(n+10);
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++)
        {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

力扣1173第N个泰波那契数

泰波那契序列 Tn 定义如下:

T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2

给你整数 n,请返回第 n 个泰波那契数 Tn 的值。

示例 1:

输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4
示例 2:

输入:n = 25
输出:1389537

提示:

0 <= n <= 37
答案保证是一个 32 位整数,即 answer <= 2^31 - 1。

题目分析

根据公式求得第n个数

解题思路

利用动态规划的思想
注意数组越界的问题,初始化定义dp数组的大小要为n+3
优化可以用滚动数组的思想:因为第4个数总等于他的前三个数相加,我们可以设置p,q,r作为每次存储前三个数的容器,每次求出新的值就不断滚动容器

代码实现

class Solution {
public:
    int tribonacci(int n) {
        vector<int>dp(n+3,0);
        dp[0]=0;
        dp[1]=1;
        dp[2]=1;
        for(int i=3;i<=n;i++)
        {
            dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
        }
        return dp[n];
    }
};

代码优化

class Solution {
    public int tribonacci(int n) {
        if (n == 0) {
            return 0;
        }
        if (n <= 2) {
            return 1;
        }
        int p = 0, q = 0, r = 1, s = 1;
        for (int i = 3; i <= n; ++i) {
            p = q;
            q = r;
            r = s;
            s = p + q + r;
        }
        return s;
    }
}

力扣70爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。

1 阶 + 1 阶
2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

1 阶 + 1 阶 + 1 阶
1 阶 + 2 阶
2 阶 + 1 阶
提示:

1 <= n <= 45

题目分析

一共有n阶楼梯,一次能爬1个或2个台阶,求有多少种方法能爬上楼顶

解题思路

爬第n次楼梯时,第n-1次爬楼梯一定走了1阶或2阶楼梯
我们用f(x)代表爬到第x阶楼梯的方案个数,所以f(x)=f(x-1)+f(x-2)
再利用滚动数组优化
理解:因为一次只能爬1个或2个台阶,所以爬第x阶台阶方案的种类来自于爬第x-1阶台阶方案数与爬第x-2阶台阶方案数之和,换句话说等于迈一步方案数和迈2步方案数之和

代码实现

class Solution {
public:
    int climbStairs(int n) {
        int p=0,q=0,r=1;
        for(int i=0;i<n;i++)
        {
            p=q;
            q=r;
            r=p+q;
        }
        return r;
    }
};

力扣746使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。

支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。
示例 2:
输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。

支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
提示:

2 <= cost.length <= 1000
0 <= cost[i] <= 999

题目分析

可以从下标为0或1的台阶开始爬,一次只能爬一个或两个台阶,求爬到楼顶的最小花费

解题思路

利用动态规划的思想
定义子问题:爬到第i个台阶的最小花费
求出递推公式:
爬到第i阶台阶之前有可能爬了1阶或2阶台阶
爬了1阶也就是爬到i-1阶,要支付cost[i-1]并加上前i-1阶需要支付的最小费用dp[i-1]
爬2阶同理
取两种情况的最小值
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
因为可选择起始点为0或1,所以dp[0]=0,dp[1]=0
注意:n为爬到楼顶的次数,所以定义dp数组大小应为n+1,表示一共有n+1层

代码实现

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n=cost.size();
        int sum=0;
        vector<int>dp(n+1,0);
        dp[0]=0;
        dp[1]=0;
        for(int i=2;i<=n;i++){
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[n];
    }
};

力扣198打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 400

题目分析

n个房屋,小偷不能同时偷相邻的两个屋子,求一晚上偷得的金钱最大化

解题思路

利用动态规划
1.定义子问题:将问题的规模缩小,子问题就是 “从 k 个房子中能偷到的最大金额 ”,用 f(k) 表示。
2.写出子问题的递推关系:f(k)=max{f(k−1),nums(k−1)+f(k−2)}
f(k)表示偷前k个房间达到的最大金额
偷房间可分为两种方案,一是直接取前k-1个房间的最大金额,即最后一个房间不偷,f(k)=f(k-1),二是偷最后一个房,并取前k-2个房间的最大金额,最后取最大值
当 k=0时,没有房子,所以 f(0)=0
当 k=1时,只有一个房子,偷这个房子即可,所以 f(1)=nums[0]

代码实现

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) {
        return 0;
    }
    // 子问题:
    // f(k) = 偷 [0..k) 房间中的最大金额
    int N = nums.size();
    vector<int> dp(N+1, 0);
    dp[0] = 0;
    dp[1] = nums[0];
    for (int k = 2; k <= N; k++) {
        dp[k] = max(dp[k-1], nums[k-1] + dp[k-2]);
    }
    return dp[N];
    }
};
}

力扣740删除并获得整数

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:

输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:

输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

提示:

1 <= nums.length <= 2 * 104
1 <= nums[i] <= 104

题目分析

选择一个任意点数nums[i]删除并获得他的点数,同时删除等于nums[i]+1和nums[i]-1的所有点数,此次删除并不能获得点数,即损失了他们的点数,求能获得的最大点数

解题思路

利用动态规划的思想
当我们选择了一个点数之后,要把这个点数都选完才能最大减少损失,并且这个点数的左右相邻点数都将损失,即我们不能选择连续的点数
听到这里大家有没有一丝熟悉的感觉,没错,与打家劫舍的问题类似
1.首先我们求出数组中的最大点数,另定义一个数组sum用来存放每个点数与其出现次数的乘积
2.令定义一个函数与打家劫舍源码一样
3.将sum代入函数中,返回动态规划的值

代码实现

class Solution {
private:
    int rob(vector<int>&nums){
        int n=nums.size();
        vector<int>dp(n+1,0);
        dp[0]=0;
        dp[1]=nums[0];
        for(int i=2;i<=n;i++)
        {
            dp[i]=max(dp[i-1],dp[i-2]+nums[i-1]);
        }
        return dp[n];
    }
public:
    int deleteAndEarn(vector<int>& nums) {
        int maxval=0;
        for(int val:nums)
        {
            maxval=max(maxval,val);
        }
        vector<int>sum(maxval+1);
        for(int val:nums)
        {
            sum[val]+=val;
        }
        return rob(sum);
    }
};

力扣118杨辉三角

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:

输入: numRows = 1
输出: [[1]]

提示:

1 <= numRows <= 30

题目分析

输出前n行的杨辉三角

解题思路

根据第i行j列的数等于第i-1行j列与第i-1行j-1列之和可列出公式,因为最左边一列的j-1列都为0,所以可以保证都为1,而最右边一列的j列都为0,同样也可以保证
注意:力扣的官方题解给的第二层for循环进入条件为j<=n,但是我们可以发现每一层的数字个数都小于等于层数,我们可以将其改为j<=i,大大减少运行时间

代码实现

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>>gen;
        int dp[32][32]={0};
        dp[0][0]=1;
        for(int i=1;i<=numRows;i++){
            vector<int>temp;
            for(int j=1;j<=i;j++){
                dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
                if(dp[i][j]){
                    temp.push_back(dp[i][j]);
                }
            }
            gen.push_back(temp);
        }
        return gen;
    }
};

;