写在前头
如果说大厂笔、面试题有迹可循的话,那最爱考的就是dfs、bfs和动规了,今天开始做动规的专项训练,它值得一个开场白。一刷LeetCode初遇动规的时候,是看的代码随想录卡哥的背包问题,卡哥将捉摸不定的动规题目总结出了一个模板,非常适合初学动规的小伙伴们去学习、揣摩,不要觉得背模板掉价,咱普通人写代码,就是一边抄一边写,抄多了,那些框架和手法就成自己的东西了。
代码随想录——动态规划理论基础
LeetCode 509. 斐波那契数
题目链接:509. 斐波那契数
思路:依据代码随想录总结的动态规划五部曲,分别是:
- 确定dp数组以及下标的含义;
- 确定递推公式;
- dp数组初始化;
- 确定遍历顺序;
- 举例推导dp数组。
再结合这道题,首先我们可以不使用dp数组,使用滚动的两个数来代替(类似滚动数组的思想);
递推公式在题目中已经给出:
F
(
n
)
=
F
(
n
−
1
)
+
F
(
n
−
2
)
F(n) = F(n - 1) + F(n - 2)
F(n)=F(n−1)+F(n−2),所以当前数就是它的前两个数之和;
初始化也在题目中给出,第一个数字是0,第二个数字是1;
从前向后遍历n次;
输出结果。
Python版本:
class Solution:
def fib(self, n: int) -> int:
a, b = 0, 1
for _ in range(n):
a, b = a+b, a
return a
时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( 1 ) O(1) O(1)
go版本:
func fib(n int) int {
a, b := 0, 1
for i:=0; i<n; i++ {
a, b = a+b, a
}
return a
}
LeetCode 70. 爬楼梯
题目链接:70. 爬楼梯
思路:重复记忆一下我们的动态规划五部曲:
- 确定dp数组以及下标的含义;
- 确定递推公式;
- dp数组初始化;
- 确定遍历顺序;
- 举例推导dp数组。
dp数组就是爬上第n个台阶有几种方法;
递推公式根据题目场景想一下,想爬上第n阶台阶的话,只能从第n-1阶爬上来,或是从第n-2阶爬上来这两种情况,所以爬上第n阶台阶的方法就是能爬到第n-1阶的方法数加能爬到第n-2阶的方法数之和,本题的递推公式:
F
(
n
)
=
F
(
n
−
1
)
+
F
(
n
−
2
)
F(n) = F(n - 1) + F(n - 2)
F(n)=F(n−1)+F(n−2),与斐波那契数列恰好一致;
初始值情况为,爬上第1阶台阶有1种方法,爬上第2阶台阶有2种方法;
从前向后,从索引2遍历至n-1;
返回dp数组的最后一个值。
Python版本:
class Solution:
def climbStairs(self, n: int) -> int:
a = 1
b = 2
if n<=3:
return n
for _ in range(n-2):
b, a = a+b, b
return b
时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( 1 ) O(1) O(1)
go版本(使用dp数组):
func climbStairs(n int) int {
if n<=3 {
return n
}
dp := make([]int, n)
dp[0] = 1
dp[1] = 2
for i:=2; i<n; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
return dp[n-1]
}
LeetCode
题目链接:
思路:重要的思想记三遍:
- 确定dp数组以及下标的含义;
- 确定递推公式;
- dp数组初始化;
- 确定遍历顺序;
- 举例推导dp数组。
dp数组就是爬上第n个台阶的最低花费,但本题有个大坑,要越过最后一阶台阶才算爬上楼梯顶部,所以dp数组要比cost数组的长度加1,表示越过cost数组的最后一个台阶爬上顶部;
递推公式根据题目场景想一下,想爬上第n阶台阶的话,只能从第n-1阶爬上来,或是从第n-2阶爬上来这两种情况,所以爬上第n阶台阶的最低花费就是从上述两种情况中选一个花费低的,本题的递推公式:
d
p
[
i
]
=
m
i
n
(
d
p
[
i
−
1
]
+
c
o
s
t
[
i
−
1
]
,
d
p
[
i
−
2
]
+
c
o
s
t
[
i
−
2
]
)
dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2]);
初始值情况为,可以选择从从下标为 0 或下标为 1 的台阶开始爬楼梯,所以 dp[0] 和 dp[1] 都是0;
从前向后,从索引2遍历至n,因为要越过最后一阶台阶,可以想象成 cost 后面才是楼梯顶部;
返回dp数组的最后一个值。
Python版本:
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
n = len(cost)
if n==2:
return min(cost)
dp = [0] * (n+1)
for i in range(2, n+1):
dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
return dp[-1]
时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( n ) O(n) O(n)
go版本:
func minCostClimbingStairs(cost []int) int {
if len(cost)==2 {
return min(cost[0], cost[1])
}
dp := make([]int, len(cost)+1)
for i:=2; i<=len(cost); i++ {
dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
}
fmt.Println(dp)
return dp[len(dp)-1]
}
func min(i, j int) int {
if i<j {
return i
} else {
return j
}
}