理论基础:
题一:509. 斐波那契数
链接
视频总结
关键点
编程思路
Me:
卡尔:
1. 确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
2. 确定递推公式
为什么这是一道非常简单的入门题目呢?
因为题目已经把递推公式直接给我们了:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];
3. dp数组如何初始化
题目中把如何初始化也直接给我们了,如下:
dp[0] = 0;
dp[1] = 1;
4. 确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
5. 举例推导dp数组
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:
0 1 1 2 3 5 8 13 21 34 55
如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的。
力扣实战
思路一:暴力数学法
class Solution:
def fib(self, n: int) -> int:
# easy
res = [0,1]
for i in range(2,n+1):
res.append(res[i-1]+res[i-2])
return res[n]
# 反思1:
思路二:递归
class Solution:
def fib(self, n: int) -> int:
# 递归写法,注意开头剪枝,参数返回值;退出条件;单词递归逻辑的书写!
res = [0,1]
if n<2: #开头剪枝
return res[n]
def func(n): #参数为n,全局变量res不需要返回值
if len(res)==n+1: #退出条件
return
sum1 = res[-1]+res[-2] #单词递归逻辑
res.append(sum1)
func(n)
func(n)
return res[n]
思路三:dp法 和 拉满递归法
# 动态规划 (注释版。无修饰)
class Solution:
def fib(self, n: int) -> int:
# 排除 Corner Case
if n == 1:
return 1
if n == 0:
return 0
# 创建 dp table
dp = [0] * (n + 1)
# 初始化 dp 数组
dp[0] = 0
dp[1] = 1
# 遍历顺序: 由前向后。因为后面要用到前面的状态
for i in range(2, n + 1):
# 确定递归公式/状态转移公式
dp[i] = dp[i - 1] + dp[i - 2]
# 返回答案
return dp[n]
# 递归实现
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
return self.fib(n - 1) + self.fib(n - 2)
文档总结
1. 简单题目是用来加深对解题方法论的理解的。
2.
题二:70. 爬楼梯ez
链接
视频总结
编程思路
Me:
- 按照回溯五部曲的思路,考虑每个dp元素的意思,然后再考虑每个dp元素直接的递推关系,这样就会把思路引导到,到n阶的爬法和到n-i阶楼梯的爬法之间的关系,然后就显然了!
卡尔:
力扣实战
思路一:
# 空间复杂度为O(n)版本
class Solution:
def climbStairs(self, n: int) -> int:
# dp[i] 为第 i 阶楼梯有多少种方法爬到楼顶
dp = [0]*(n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
class Solution:
def climbStairs(self, n: int) -> int:
#可以分解成先走n-2再走2和先走n-1再走1
dp = [1,2]
for i in range(2,n):
dp.append(dp[-1]+dp[-2])
return dp[n-1]
# 反思1:
思路二:
# 空间复杂度为O(1)版本,相当于只维护两个需要计算的空间,不断的计算和覆盖。这也是优化空间复杂度的思路
class Solution:
def climbStairs(self, n: int) -> int:
dp = [0]*(n+1)
dp[0] = 1
dp[1] = 1
for i in range(2,n+1):
tmp = dp[0] + dp[1]
dp[0] = dp[1]
dp[1] = tmp
return dp[1]
思路三:
这道题目还可以继续深化,就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。
这又有难度了,这其实是一个完全背包问题,但力扣上没有这种题目,所以后续我在讲解背包问题的时候,今天这道题还会从背包问题的角度上来再讲一遍。 如果想提前看一下,可以看这篇:70.爬楼梯完全背包版本(opens new window)
代码:
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) { // j的上界是m,这是关键把m换成2,就可以AC爬楼梯这道题
if (i - j >= 0) dp[i] += dp[i - j]; #因为是方法数量,所以磊加即可
}
}
return dp[n];
}
};
代码中m表示最多可以爬m个台阶。
以上代码不能运行哈,我主要是为了体现只要把m换成2,粘过去,就可以AC爬楼梯这道题,不信你就粘一下试试。
此时我就发现一个绝佳的大厂面试题,第一道题就是单纯的爬楼梯,然后看候选人的代码实现,如果把dp[0]的定义成1了,就可以发难了,为什么dp[0]一定要初始化为1,此时可能候选人就要强行给dp[0]应该是1找各种理由。那这就是一个考察点了,对dp[i]的定义理解的不深入。
然后可以继续发难,如果一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。这道题目leetcode上并没有原题,绝对是考察候选人算法能力的绝佳好题。
这一连套问下来,候选人算法能力如何,面试官心里就有数了。
其实大厂面试最喜欢的问题就是这种简单题,然后慢慢变化,在小细节上考察候选人。
文档总结
1.
题三:746. 使用最小花费爬楼梯
链接
视频总结
力扣实战
思路一:
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
#dp[i]表示爬到第i个台阶后面的最小花费
n=len(cost)
if n==2:
return min(cost[0],cost[1])
dp = [999]*(n+1)
dp[0]=0
dp[1]=cost[0]
dp[2]=min(cost[0],cost[1]) #[0,1,1,2,2,3,3,4,4,5,6]
dp[3]=min(cost[1],dp[2]+cost[2]) #因为开始可以从索引0或者1跳一格或者两个,则最初最多能跳到索引3,所以需要初始化四个参数
for i in range(4,n+1): #注意取值上界!卡了好久,煞笔了
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
res=dp[-1]
return res
# 反思1:
思路二:
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
#基于基础dp思路,把空间复杂度减少到欧一,由于状态方程在n≥4之后才起作用,所以需要维护后面三个空间
n=len(cost)
if n==2:
return min(cost[0],cost[1])
if n<5:
dp = [999]*(n+1)
dp[0]=0
dp[1]=cost[0]
dp[2]=min(cost[0],cost[1]) #[0,1,1,2,2,3,3,4,4,5,6]
dp[3]=min(cost[1],dp[2]+cost[2])
for i in range(4,n+1):
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
else:
dp = [999]*6
dp[0]=0
dp[1]=cost[0]
dp[2]=min(cost[0],cost[1]) #[0,1,1,2,2,3,3,4,4,5,6]
dp[3]=min(cost[1],dp[2]+cost[2]) #因为开始可以从索引0或者1跳一格或者两个,则最初最多能跳到索引3,所以需要初始化四个参数
for i in range(4,6):
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
for i in range(6,n+1): #注意取值上界!卡了好久,煞笔了
dp[-3]=dp[-2]
dp[-2]=dp[-1]
tem=min(dp[-2]+cost[i-1],dp[-3]+cost[i-2])
dp[-1]=tem
res=dp[-1]
return res
思路三
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
#若清晰了dp0和dp1的含义,代码可以极大简化.然后在此基础上优化空间,只需要欧一的空间复杂度
n=len(cost)
dp=[0]*(2)
for i in range(2,n+1):
tem=min(dp[-1]+cost[i-1],dp[-2]+cost[i-2])
dp[0]=dp[1]
dp[1]=tem
return dp[-1]
文档总结
1. 题意确实有点模糊:①若位置为零到底是否花费体力②重点是数组最后一个元素的后面
2. 本题初始化:在0位置和1位置时,不需要跳,所以初始为零,其他的位置都需要跳,所以不需要跳