LeetCode刷题day52

简介: LeetCode刷题day52

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


思路分析


动规五部曲


这里我们要用一个一维dp数组来保存递归的结果


  • 确定dp数组以及下标的含义


dp[i]的定义为:第i个数的斐波那契数值是dp[i]


  • 确定递推公式


题目已经把递推公式直接给我们了:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];


  • dp数组如何初始化


题目中把如何初始化也直接给我们了,如下: dp[0] = 0; dp[1] = 1;


  • 确定遍历顺序


从递归公式dp[i] = dp[i - 1] + dp[i - 2]; 中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的


  • 举例推导dp数组


按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:


0 1 1 2 3 5 8 13 21 34 55


如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的。


参考代码


方法一:数组模拟动归

int fib(int n) {
  vector<int> dp(31);//初始化 
  dp[0] = 0;
  dp[1] = 1;
  for(int i = 2;i <= n;i++){
    dp[i] = dp[i-1]+dp[i-2];//该式也是状态转移方程式 
  }
  return dp[n];
}

方法二:变量模拟

int fib(int n){
  if(n<=1){
    return n;
  }
  vector<int> dp(2);//定义
  dp[0] = 0;
  dp[1] = 1; 
  for(int i = 2;i<=n;i++){
    int sum = dp[0]+dp[1];
    dp[0] = dp[1];
    dp[1] = sum;
  }
  return dp[1];
} 

方法三:递归

//递归写法 
int fib(int n){
  if(n==1||n==0){
    return n;
  } 
  return fib(n-1)+fib(n-2);
}


70. 爬楼梯


题目描述


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

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


示例 1:


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


示例 2:


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


思路分析


看到这个题我们可以举几个例子


  • 楼梯1: 1 ==> 1种情况
  • 楼梯2 1+1 2 ==> 2种情况
  • 楼梯3 1+2 2+1 1+1+1 ==> 3种情况
  • 楼梯4 1+1+2 2+2 1+2+1 2+1+1 1+1+1+1 ==> 5种情况


我们发现每次楼梯数n和情况数的关系是: 爬上当前楼梯数的情况 = 上一层楼梯的情况+上上层楼梯的情况数


接下来我们用**动归五部曲**来解决这个Problem


定义一个一维数组dp来记录不同楼层的状态


确定dp数组以及下标的含义

dp[i]: 爬到第i层楼梯,有dp[i]种方法


确定递推公式

从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。


首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。


还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。


那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!即: dp[i] = dp[i - 1] + dp[i - 2]。


在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。


dp数组如何初始化

由于 n >=1,所以dp初始化: dp[1] = 1,dp[2] = 2. 当然从dp[0] = 0,也是没有问题的(正好和斐波那契规律一致)


确定遍历顺序

从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的


举例推导dp数组

举例当n为5的时候,dp (dp数组)应该是这样的

5eee8eb0170fcb5fb5415d24d9764051.png


如果代码出问题了,就把dp 打印出来,看看究竟是不是和自己推导的一样。


此时大家应该发现了,这不就是斐波那契数列么!


唯一的区别是,没有讨论dp[0]应该是什么,因为dp[0]在本题没有意义!(写了对结果也没有影响)


参考代码

#include<bits/stdc++.h>
using namespace std;
int climbStairs(int n) {
  vector<int> dp(50);
  dp[1] = 1;
  dp[2] = 2;
  for(int i = 3;i <= n; i++){
    dp[i] = dp[i-1]+dp[i-2];
  }
  return dp[n];
}


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 。

思路分析


注意题目描述:每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯


所以示例1中只花费一个15 就可以到阶梯顶,最后一步可以理解为 不用花费。


也就是说我们只要爬到最后一阶或者前一阶,然后最后一步就不用花费了


读完题大家应该知道指定需要动态规划的,因为爬楼梯是从前面的楼梯向上爬的,后面的情况数由前面决定.


动归五部曲


确定dp数组以及下标的含义


使用动态规划,就要有一个数组来记录状态,本题只需要一个一维数组dp[i]就可以了。


dp[i]的定义:到达第i个台阶所花费的最少体力为dp[i]。(注意这里认为是第一步一定是要花费,最后一步不需要花费)


对于dp数组的定义,大家一定要清晰!


确定递推公式

可以有两个途径得到dp[i],一个是dp[i-1] 一个是dp[i-2]。


那么究竟是选dp[i-1]还是dp[i-2]呢?


一定是选最小的,所以dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];


注意这里为什么是加cost[i],而不是cost[i-1],cost[i-2]之类的,因为题目中说了:每当你爬上一个阶梯你都要花费对应的体力值


dp数组如何初始化

根据dp数组的定义,dp数组初始化其实是比较难的,因为不可能初始化为第i台阶所花费的最少体力。


那么看一下递归公式,dp[i]由dp[i-1],dp[i-2]推出,既然初始化所有的dp[i]是不可能的,那么只初始化dp[0]和dp[1]就够了,其他的最终都是dp[0] dp[1]推出。


所以初始化代码为:


vector<int> dp(cost.size());
dp[0] = cost[0];
dp[1] = cost[1];

确定遍历顺序


最后一步,递归公式有了,初始化有了,如何遍历呢?


因为是模拟台阶,而且dp[i]又dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组就可以了。


但是稍稍有点难度的动态规划,其遍历顺序并不容易确定下来。


例如:01背包,都知道两个for循环,一个for遍历物品嵌套一个for遍历背包容量,那么为什么不是一个for遍历背包容量嵌套一个for遍历物品呢? 以及在使用一维dp数组的时候遍历背包容量为什么要倒序呢?这些在后面将详细的介绍


举例推导dp数组


拿示例2:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] ,来模拟一下dp数组的状态变化,如下:


57aad02763b82209762983d98de40601.png

如果大家代码写出来有问题,就把dp数组打印出来,看看和如上推导的是不是一样的。


参考代码

#include<bits/stdc++.h>
using namespace std;
int minCostClimbingStairs(vector<int>& cost) {
  vector<int> dp(cost.size());//定义dp
  dp[0] = cost[0];//初始化dp(相当于第一步已经支付过了.其实第一步是不需要花费的,这也导致了,
    //我们认为第一步需要花费那么最后一步就认为不用花费.只需要走到最后一阶或者前一阶就可)
  dp[1] = cost[1];
  for(int i = 2; i < cost.size(); i++) {// 计算dp
    dp[i] = min(dp[i-1],dp[i-2]) +cost[i];//状态转移方程
  }
  return min(dp[cost.size()-1],dp[cost.size()-2]);//最后一步不需要支付费用,因为已经支付过了.
}


相关文章
|
11天前
|
索引
【力扣刷题】两数求和、移动零、相交链表、反转链表
【力扣刷题】两数求和、移动零、相交链表、反转链表
18 2
【力扣刷题】两数求和、移动零、相交链表、反转链表
|
10天前
|
算法
"刷题记录:哈希表+双指针 | leetcode-2465. 不同的平均值数目 "
该文段是一篇关于编程题目的解答,主要讨论如何找到数组中所有不同平均值的个数。作者首先使用排序和哈希集来解决,将数组转为列表排序后,通过双指针计算平均值并存入哈希集以去重。然后,作者发现可以优化方案,通过双指针在排序后的数组中直接计算两数之和,用哈希集记录不重复的和,从而避免实际计算平均值,提高了算法效率。最终代码展示了这两种方法。
20 0
|
11天前
|
索引
【力扣刷题】删除链表的倒数第 N 个结点、两两交换链表中的节点、随机链表的复制
【力扣刷题】删除链表的倒数第 N 个结点、两两交换链表中的节点、随机链表的复制
17 0
|
11天前
|
存储 算法 索引
【力扣刷题】只出现一次的数字、多数元素、环形链表 II、两数相加
【力扣刷题】只出现一次的数字、多数元素、环形链表 II、两数相加
23 1
|
11天前
【力扣刷题】二叉树的中序遍历、二叉树的最大深度、翻转二叉树、对称二叉树
【力扣刷题】二叉树的中序遍历、二叉树的最大深度、翻转二叉树、对称二叉树
18 0
|
11天前
|
索引
【力扣刷题】数组实现栈、后缀表达式(逆波兰表达式)求值、中缀表达式转换为后缀表达式(无括号&&有括号)
【力扣刷题】数组实现栈、后缀表达式(逆波兰表达式)求值、中缀表达式转换为后缀表达式(无括号&&有括号)
16 0
|
11天前
|
索引
【力扣刷题】回文链表、环形链表、合并两个有序链表
【力扣刷题】回文链表、环形链表、合并两个有序链表
16 0
|
13天前
|
算法 索引
力扣刷题【第一期】
这是一个关于算法的总结,包含7个不同的问题。1)爬楼梯问题,使用动态规划,通过迭代找到到达n阶楼梯的不同方法数。2)两数之和,通过双重循环找出数组中和为目标值的两个数的索引。3)移动零,使用双指针将数组中的0移到末尾。4)合并有序链表,创建新链表按升序合并两个链表。5)删除链表重复值,遍历链表删除重复元素。6)环形链表检测,使用快慢指针判断链表是否有环。7)相交链表,计算链表长度找
18 1
|
13天前
|
存储 Java
JAVA数据结构刷题 -- 力扣二叉树
JAVA数据结构刷题 -- 力扣二叉树
20 0
|
23天前
|
算法 C++
【刷题】Leetcode 1609.奇偶树
这道题是我目前做过最难的题,虽然没有一遍做出来,但是参考大佬的代码,慢慢啃的感觉的真的很好。刷题继续!!!!!!
15 0