动态规划简介
什么是动态规划
动态规划是运筹学中用于求解决策过程中的最优化的数学方法。如果一个问题可以分解成若干个子问题,并且子问题之间还有重叠的更小的子问题,就可以考虑用动态规划来解决这个问题。
应用动态规划之前要分析能否把大问题分解成小问题,分解后的每个小问题也存在最优解。如果将小问题的最优解组合起来能够得到整个问题的最优解,那么就可以使用动态规划解决问题。
可以应用动态规划求解的问题主要由四个特点:
- 问题是求最优解
- 整体问题的最优解依赖于各个子问题的最优解
- 大问题分解成若干小问题,这些小问题之间还有相互重叠的更小的子问题
- 从上往下分析问题,从下往上求解问题
动态规划和递归区别
动态规划利用申请的空间来记录每一个暴力搜索的计算结果,下次需要这个结果的时候直接使用。
动态规划和分治区别
适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的,下一个子阶段的求解是建立在上一个子阶段的解的基础上
动态规划解决步骤
1, 确定状态
2, 转移方程
3, 初始条件和边界条件
4, 计算顺序
动态规划类别
1, 坐标型
2, 位操作型
3, 背包型
4, 序列型
5, 划分型
6, 博弈型
7, 区间型
8, 双序列型
1. 坐标型动态规划
给定一个序列或者网格,找序列中某个子序列或网格中某条路径
62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向右 -> 向下
- 向右 -> 向下 -> 向右
- 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
提示:
1 <= m, n <= 100
题目数据保证答案小于等于 2 * 10 ^ 9
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-paths
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
一,确定状态
1,最后一步:无论机器人用何种方式到达右下角,总有最后挪动得一步:向右或者向下
2,右下角坐标设为(m-1,n-1)
3,那么前一步机器人一定是在(m-2, n-1)或者(m-1, n-2)
子问题:
那么,如果机器人有X中方法从左上角走到(m-2, n-1),有Y种方式从左上角走到(m-1, n-2),则机器人有X+Y种方式走到(m-1, n-1)
二,转移方程
三,初始条件和边界条件
初始条件:i = 0 且 j = 0,f[i][j] = 1, 因为机器人只有一种方式到左下角
边界条件:i = 0 或者 j = 0,则前一步只能有一个方向过来,因此f[i][j] = 1
四,计算顺序
f[0][0] = 0
f[0][0], f[0][1], …, f[0][n-1]
f[1][0], f[1][1], …, f[1][n-1]
• …
•计算前m-1行:f[m-1][0], f[m-1][1], …, f[m-1][n-1]
•最后答案:f[m-1][n-1]
五,复杂度分析
•时间复杂度:O(MN)
空间复杂度:O(MN)
六,代码分析
int uniquePaths(int m, int n){
int **table = (int **)malloc(sizeof(int *) * n);
for (int i = 0; i < n; i++)
{
table[i] = malloc(sizeof(int) * m);
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if(i == 0 || j == 0)
{
table[i][j] = 1;
continue;
}
table[i][j] = table[i][j - 1] + table[i - 1][j];
}
}
return table[n - 1][m - 1];
}
Python3的实现方法:
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
matrix = [[0 for i in range(n)] for j in range(m)]
for i in range(m):
for j in range(n):
if i == 0 or j == 0:
matrix[i][j] = 1
else:
matrix[i][j] = matrix[i-1][j] + matrix[i][j-1]
return matrix[m-1][n-1]
2. 位操作型动态规划
- 比特位计数
一,确定状态
观察一个数的二进制位:(170)10=(10101010)2
最后一步:观察这个数最后一个二进制位(最低位),去掉它,在统计还剩余多少个1;
(171)10=(10101011)2
(85)10=(1010101)2
85的二进制表示里有4个1,170的二进制表示里面也是4个1
要求求N的二进制表示中有多少个1,在N的二进制去掉最后一位N mod 2,设新的数是Y = (N>>1),需要知道Y二进制表示中有多少个1,则f(N) = (N mod 2) + f(Y)
二,转移方程
三,初始条件和边界条件
设f[i]表示i的二进制表示中有多少个1
初始条件:f[0] = 0
f[i] = f[i>>1] + (i mod 2)
四,计算顺序
f[0], f[1], f[2], …, f[N]
五,复杂度分析
时间复杂度:O(N)
空间复杂度:O(N)
六,代码分析
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* countBits(int num, int* returnSize){
int *dg = (int *)malloc(sizeof(int) * (num + 1));
*returnSize = num + 1;
dg[0] = 0;
for (int i = 1; i <= num; i++) {
dg[i] = (i & 1) + dg[i>>1];
}
return dg;
}
Python3的实现方式:
lass Solution:
def countBits(self, num: int) -> List[int]:
ans = [0 for i in range(num + 1)]
ans[0] = 0
for i in range(num + 1):
ans[i] = (i & 1) + ans[i >> 1]
return ans
3. 背包型动态规划
- 背包问题
描述
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]
样例 1:
输入: [3,4,8,5], backpack size=10
输出: 9
样例 2:
输入: [2,3,5,7], backpack size=12
输出: 12
挑战
O(n x m) 的时间复杂度 and O(m) 空间复杂度
如果不知道如何优化空间O(n x m) 的空间复杂度也可以通过.
一,确定状态
需要知道前N个物品能否拼出重量W (W =0, 1, …, M)
最后一步:最后一个物品(重量AN-1)能否进入背包
1. 情况一:如果前N-1个物品能拼出W,当然前N个物品也能拼出W
2. 情况二:如果前N-1个物品能拼出W- AN-1 ,再加上最后的物品AN-1 ,拼出W
例子:四个物品:重量分别为2,3,5,7
前三个物品可以拼出重量8 (3+5),自然4个物品也可以拼出重量8
前3个物品可以拼出重量2 (2),加上最后一个物品,可以拼出重量9
状态:设f[i][w] = 能否用前i个物品拼出重量w (TRUE / FALSE)
常见误区:错误设f[i]表示前i个物品能拼出的最大重量(不超过M)
– 反例:A=[3 9 5 2], M=10
– 错误原因:最优策略中,前N-1个物品拼出的不一定是不超过M的最大重量
二,转移方程
三,初始条件和边界条件
f[i][w] = f[i-1][w] OR f[i-1][w-Ai-1]
• 初始条件:
– f[0][0] = TRUE: 0个物品可以拼出重量0
– f[0][1…M] = FALSE: 0个物品不能拼出大于0的重量
• 边界情况:
– f[i-1][w-Ai-1]只能在w≥Ai-1时使用
四,计算顺序
• 初始化f[0][0], f[0][1], …, f[0][M]
• 计算前1个物品能拼出哪些重量:f[1][0], f[1][1], …, f[1][M]
• 计算前2个物品能拼出哪些重量: f[2][0], f[2][1], …, f[2][M]
• …
• 计算前N个物品能拼出哪些重量:f[N][0], f[N][1], …, f[N][M]
五,复杂度分析
时间复杂度:O(MN)
空间复杂度:O(MN) 优化后可以达到O(M)
六,代码分析
class Solution:
"""
@param m: An integer m denotes the size of a backpack
@param A: Given n items with size A[i]
@return: The maximum size
"""
def backPack(self, m, A):
n = len(A)
if n <= 0 or m <= 0:
return 0
print("m:%d n:%d\n"%(m, n))
dp = [[0 for i in range(m + 1)] for j in range(n + 1)]
dp[0][0] = True
for i in range(1, m + 1):
dp[0][i] = False
for i in range(1, n + 1):
for j in range(m + 1):
dp[i][j] = dp[i - 1][j]
if j >= A[i - 1]:
dp[i][j] |= dp[i - 1][j - A[i - 1]]
res = 0
for i in range(m, 0, -1):
if dp[n][i] == True:
res = i;
break
return res