动态规划
简称DP【Dynamic Programming】,是求解最优化问题的一种常用策略。
通常的使用套路(一步一步优化)
①暴力递归(自顶向下,出现了重叠子问题)
②记忆化搜索(自顶向下)
③递推(自底上向)
【来自维基百科的解释】:Dynamic Programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems , solving each of those subproblems just once , and storing their solutions.
①将复杂的原问题拆解成若干简单的子问题
②每个子问题仅仅解决1次,并保存他们的解
③最后推导出原问题的解
动态规划的常规步骤
动态规划中的“动态”可以理解为是“会变化的状态”。
①定义状态(状态是原问题,子问题的解)
比如定义dp(i)的含义
②设置初始状态(边界)
比如设置dp(0)的值
③确定状态转移方程
比如确定dp(i)与dp(i-1)的关系
可以使用动态规划解决的问题,通常具有2个特点:
1、最有子结构(最优化原理):通常求解子问题的最优解,可以获得原问题的最优解。
2、无后效性
①某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响(过去与未来无关)
②在推导后面阶段的状态时,只关心前面阶段的具体状态值,不关心这个状态是怎么一步步推导过来的。
举个例子:
上图中:
1、从起点(0,0)走到终点(4,4)一共有多少种走法?只能向下、向右走。
假设dp(i, j)是从(0, 0) 走到 ( i, j ) 的走法
初始条件:dp(i, 0) = dp(0, j) = 0
状态转移方程:dp(i, j) = dp( i, j - 1) + dp( i - 1, j)
无后效性
推导 dp(i, j) 时只需要用到 dp(i, j – 1) 、dp(i – 1, j) 的值
不需要关心 dp(i, j – 1) 、dp(i – 1, j) 的
2、如果可以向左、右上下走 ,并且同一个格子不能2 次【因为上下左右均可走,即可以绕圈走,但是一个格子不能走两次,就导致后面的要考虑前面的走法,保证一个格子只能走一次】
有后效性
dp(i, j) 下一步要怎么走,还关心上一步是来的
也就是还要关心 dp(i, j – 1) 、dp(i – 1, j)是怎么来的?
练习1-找零钱
leetcode_322_ 零钱兑换: https://leetcode-cn.com/problems/coin-change/
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
示例 4:
输入:coins = [1], amount = 1
输出:1
示例 5:
输入:coins = [1], amount = 2
输出:2
假设有 25 分、 20 分、 5分、 1分的硬币,现要找给客户 41分的零钱,如何办到硬币的数量最少?
此题中,设dp(n) 为凑到 n 分需要的最少的硬币的数量。
1、如果第一次选择了25分的硬币,那么dp(n) = dp(n - 25) + 1;
2、如果第一次选择了20分的硬币,那么dp(n) = dp(n - 20) + 1;
3、如果第一次选择了5分的硬币,那么dp(n) = dp(n - 5) + 1;
4、如果第一次选择了1分的硬币,那么dp(n) = dp(n - 1) + 1;
所以,dp(n) = min{ dp(n - 25), dp(n - 20), dp(n - 5), dp(n - 1)} + 1。初始条件dp(0) = 0;
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new in