509. 斐波那契数
这个题目根据题意,暴力解法就是一个简单的递归,求$fib(n)$的值,我们求$fib(n-1)+fib(n-2)$就可以了。当n是0,1,2的时候就是fib函数的一个基础状态。终止递归的条件。但是这个效率很低,时间复杂度是$O(2^n)$。
class Solution:
def fib(self, n: int) -> int:
if n == 0:
return 0
if n == 1:
return 1
if n == 2:
return 1
return self.fib(n - 1) + self.fib(n - 2)但是我们可以发现,效率低的原因是造成了多次的重复计算,例如我们求$fib(5)$,就要求$fib(4)$和$fib(3)$,而$fib(4)$又需要求$fib(3)$和$fib(2)$,这里的$fib(3)$就重复计算了,包括其他的一些中间状态也有可能会被多次计算。那我们就可以做一个备忘录,要计算一个值,先看看之前是不是算过了,算过就不用重复算了,如果没有算过,就算一下,存到备忘录里面,然后再返回这个值。因为没有了重复计算,每个$fib(n)$都只需要计算一次就行了。时间复杂度直接从$O(2^n)$降低到了$O(n)$,提升非常的巨大。
class Solution:
def fib(self, n: int) -> int:
memo = [-1 for _ in range(n + 1)]
return self.func(n, memo)
def func(self, n, memo):
if n == 0:
return 0
elif n == 1:
return 1
elif n == 2:
return 1
if memo[n] != -1:
return memo[n]
memo[n] = self.func(n - 1, memo) + self.func(n - 2, memo)
return memo[n]当然,上面是递归的写法,我们还可以使用一个简单的for循环通过迭代的写法来解决这个问题。递归是自顶向下,不断的分解问题,迭代是自底向上,从下面往上推出最终的结果。这样的话,我们连备忘录memo也可以进行优化了,我们只需要知道$fib(n-1)$和$fib(n-2)$的状态就可以了。使用两个变量,然后进行更新替换即可。
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
fib_n_1 = 1
fib_n_2 = 0
fib_n = 0
for i in range(2, n + 1):
fib_n = fib_n_1 + fib_n_2
fib_n_2 = fib_n_1
fib_n_1 = fib_n
return fib_n322. 零钱兑换
这个题的状态就是amount,金额的总数,选择就是当我们选了一个硬币,会导致金额变动。dp函数的定义就是返回凑够目标金额最少的硬币数量。例如题目给的[1,2,5]三种面额金币,凑11,那我们求11-1=10,面额为10最少需要x枚硬币,求11-2=9,面额为9最少需要y枚硬币,求11-5=6,面额为6最小需要z枚金币,然后求max(x,y,z)的最小值,加上我们这次选择的硬币,就是最小值了。base case就是不停的往下凑,刚好能凑到0,那就是可以凑出来,如果凑的值小于0,那就是不符合条件了。最后再加上备忘录功能,如果一个金额,最小需要多少枚硬币凑齐已经算过,就不用重复计算了。这是递归的解法。
class Solution:
def __init__(self):
self.memo = []
def coinChange(self, coins: List[int], amount: int) -> int:
self.memo = [-666] * (amount + 1)
return self.dp(coins, amount)
def dp(self, coins, amount):
if amount == 0:
return 0
if amount < 0:
return -1
if self.memo[amount] != -666:
return self.memo[amount]
res = float("inf")
for coin in coins:
subProblem = self.dp(coins, amount - coin)
if subProblem == -1:
continue
res = min(res, subProblem + 1)
self.memo[amount] = res if res != float("inf") else -1
return self.memo[amount]会了递归,转换成迭代也就不难了,创建一个备忘录数组。外层for循环求的是dp数组,内层for循环是尝试不同的选择,然后更新dp[i]这个地方的答案。这里的dp数组默认值是amount+1就可以,结果肯定不会超过amount的。作为一个区分。
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [amount + 1] * (amount + 1)
dp[0] = 0
for i in range(len(dp)):
for coin in coins:
if i - coin < 0:
continue
dp[i] = min(dp[i], 1 + dp[i - coin])
return -1 if dp[amount] == amount + 1 else dp[amount]leetcode动态规划基础练习题
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法