- 1. 概念
- 2. 打家劫舍
- 3 零钱兑换
1. 概念
关于动态规划这类问题 强烈建议学完下面的帖子:
https://blog.csdn.net/qq_16664581/article/details/89598243
理解动态规划的使用场景强烈建议读一下这个故事:
https://www.cnblogs.com/sdjl/articles/1274312.html
步骤:
- 确定问题 (可能是求某个问题的最优解 例如:背包问题中 装的物品价值最大,货币兑换问题,使用的币数量最少,或者是后一步的结果依赖前一步 的这类问题)
- 问题分解为子问题(背包问题里面第i个物品 可以选择那与不拿, 货币兑换问题,最大面值的零钱 使用与不使用)
- 写出状态转移方程
f(n) = f(n-1) + f(n-1)
f(n) = best_of[f(n-1) , f(n-1))] - 确定边界
2. 打家劫舍
链接:https://leetcode.cn/problems/house-robber/description/
假设v(i)表示打劫 0–i能够获得的最大赃款
当你来到房间i时,你有两个选择:
- 偷i,然后计算从前面i-2间里偷出的最大值,因为i-1不能再偷了。偷完把v(i)加到总赃款中。
- 不偷i,这样你可以自由地选择前面的i-1间来使得赃款最大化。因为没偷,就不能往总赃款里加值。
实现1:
from typing import List
class Solution:def rob(self, nums: List[int]) -> int:def dp(i):if i == 0: return 0if i == 1: return nums[0]val = max(dp(i - 2) + nums[i - 1], dp(i - 1))return valreturn dp(len(nums))if __name__ == '__main__':nums = [2, 7, 9, 3, 1]s = Solution()res = s.rob(nums)print(res)
实现2--加入缓存
from typing import Listclass Solution:def rob(self, nums: List[int]) -> int:cache = {}def dp(i):if i == 0: return 0if i == 1: return nums[0]if i in cache: return cache[i]val = max(dp(i - 2) + nums[i - 1], dp(i - 1))cache[i] = valreturn valreturn dp(len(nums))if __name__ == '__main__':nums = [2, 7, 9, 3, 1]s = Solution()res = s.rob(nums)print(res)
实现3--递归公式
from typing import Listclass Solution:def rob(self, nums: List[int]) -> int:a = b = 0for v in nums:a, b = b, max(a+v, b) # b赋值给a new_b赋值给breturn bif __name__ == '__main__':nums = [2, 7, 9, 3, 1]s = Solution()res = s.rob(nums)print(res)
3 零钱兑换
链接:https://leetcode.cn/problems/coin-change/description/
coins 已经从小到大排好序
定义: f(i, t)
i:面值的子集
t: 目标金额
使用面值子集i 来构成目标金额 t 所使用的硬币最小数量
面值子集i: coins[0], coins[1], … coins[i], i之后不考虑
原问题转化成: f(n-1, amount)
地推公式:
说明:
正常情况下 f(i, t)的计算是分成两支 一支向上走 一只支向下走 取这两者的较小值
假设面值集合i 里面的最大值 val=coins[i]
向上走 代表继续使用最大面值 转化成 f(i, t-val) + 1, 之所以要+1 是因为使用了一次 最大面值,特殊情况:
当t<val的时候 代表最大面值已经超过了t 无法拼出t 这是返回一个无穷大,可以将该分支剪掉, 因为min的运算 取更小值 值为无穷大的 肯定会被抛弃掉
当t=val 代表只能用一个最大面值 拼成 目标值t
其他情况 返回f(i, t-val) + 1
向右走 代表不再使用最大面值 正常情况为f(i-1, t)
当 i==0 已经不能再往右走 返回无穷大 舍弃掉该分支即可
from typing import Listclass Solution:def coinChange(self, coins: List[int], amount: int) -> int:# 异常处理if not amount:return 0mini_coin_exist = Falsefor co in coins:if co <= amount:mini_coin_exist = Trueif not mini_coin_exist:return -1if len(coins) == 1 and amount % coins[0] != 0:return -1# 核心逻辑n = len(coins)cache = {}def _dp(i, t):if (i, t) in cache:return cache[(i, t)]val = coins[i]# 使用valif t < val:use_val = float('inf')elif t == val:use_val = 1else:use_val = _dp(i, t - val) + 1# 不使用valno_val = float('inf') if i == 0 else _dp(i - 1, t)res = min(use_val, no_val)cache[(i, t)] = resreturn resresult = _dp(n - 1, amount)# 没有方案的情况if result == float("inf"):return -1return resultif __name__ == '__main__':# coins = [1, 2147483647]# amount = 2coins = [384, 324, 196, 481]amount = 285s = Solution()res = s.coinChange(coins, amount)print(res)