动态规划 java_leetcode-java/专项_动态规划.md at master · mengbd/leetcode-java · GitHub

动态规划是一种用于解决最优化问题的算法,它通过将大问题分解为子问题来求解。博客详细介绍了动态规划的三个关键要素:重叠子问题、最优子结构和状态转移方程,并通过凑零钱问题展示了如何应用动态规划。通过备忘录或DP表优化计算过程,避免重复计算,从而降低时间复杂度。最后,提供了动态规划问题的代码框架和伪码,帮助读者理解动态规划的实现思路。

动态规划

动态规划问题的一般形式就是求最值,比如说让你求最长递增子序列呀,最小编辑距离呀等等。

重叠子问题、最优子结构、状态转移方程就是动态规划三要素:

「重叠子问题」 → 如果暴力穷举的话效率会极其低下,所以需要「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。

具备「最优子结构」,才能通过子问题的最值得到原问题的最值。

动态规划的核心思想就是穷举求最值,但只有列出正确的「状态转移方程」才能正确地穷举。

辅助思考: 明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义。

代码框架:

# 初始化 base case

dp[0][0][...] = base

# 进行状态转移

for 状态1 in 状态1的所有取值:

for 状态2 in 状态2的所有取值:

for ...

dp[状态1][状态2][...] = 求最值(选择1,选择2...)

举例子: 凑零钱问题(leetcode#322)

题目:给你 k 种面值的硬币,面值分别为 c1, c2 ... ck,每种硬币的数量无限,再给一个总金额 amount,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回 -1 。

解题步骤:

确定 base case,这个很简单,显然目标金额 amount 为 0 时算法返回 0,因为不需要任何硬币就已经凑出目标金额了。

确定「状态」,也就是原问题和子问题中会变化的变量。由于硬币数量无限,硬币的面额也是题目给定的,只有目标金额会不断地向 base case 靠近,所以唯一的「状态」就是目标金额 amount。

确定「选择」,也就是导致「状态」产生变化的行为。目标金额为什么变化呢,因为你在选择硬币,你每选择一枚硬币,就相当于减少了目标金额。所以说所有硬币的面值,就是你的「选择」。

明确 dp 函数/数组的定义。我们这里讲的是自顶向下的解法,所以会有一个递归的 dp 函数,一般来说函数的参数就是状态转移中会变化的量,也就是上面说到的「状态」;函数的返回值就是题目要求我们计算的量。就本题来说,状态只有一个,即「目标金额」,题目要求我们计算凑出目标金额所需的最少硬币数量。

所以我们可以这样定义 dp 函数:dp(n) 的定义 输入一个目标金额 n,返回凑出目标金额 n 的最少硬币数量。

伪码:

# 伪码框架

def coinChange(coins: List[int], amount: int):

# 定义:要凑出金额 n,至少要 dp(n) 个硬币

def dp(n):

# 做选择,选择需要硬币最少的那个结果

for coin in coins:

res = min(res, 1 + dp(n - coin))

return res

# 题目要求的最终结果是 dp(amount)

return dp(amount)

但 总时间复杂度为 O(k * n^k),指数级别。

通过备忘录消除子问题:

def coinChange(coins: List[int], amount: int):

# 备忘录

memo = dict()

def dp(n):

# 查备忘录,避免重复计算

if n in memo: return memo[n]

# base case

if n == 0: return 0

if n < 0: return -1

res = float('INF')

for coin in coins:

subproblem = dp(n - coin)

if subproblem == -1: continue

res = min(res, 1 + subproblem)

# 记入备忘录

memo[n] = res if res != float('INF') else -1

return memo[n]

return dp(amount)

dp 数组的定义:当目标金额为 i 时,至少需要 dp[i] 枚硬币凑出。

int coinChange(vector& coins, int amount) {

// 数组大小为 amount + 1,初始值也为 amount + 1

vector dp(amount + 1, amount + 1);

// base case

dp[0] = 0;

// 外层 for 循环在遍历所有状态的所有取值

for (int i = 0; i < dp.size(); i++) {

// 内层 for 循环在求所有选择的最小值

for (int coin : coins) {

// 子问题无解,跳过

if (i - coin < 0) continue;

dp[i] = min(dp[i], 1 + dp[i - coin]);

}

}

return (dp[amount] == amount + 1) ? -1 : dp[amount];

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值