动态规划(一)

动态规划(一)

dynamic programming:此处的programming表示用的是表格法,非编程的意思 。

类似于分治,通过组合子问题的解来解决原问题。分治将问题划分互不相交的子问题,递归求解子问题,并且将子问题的解结合起来,求得原解。而动态规划要求子子问题重叠。动态规划是递归的优化。当采用递归重复调用相同子问题时,就可以使用动态规划来解决。

问题特征:

  • overlapping subproblems(子问题重叠的情况):即不同子问题具有公共的子子问题,动态规划对每个子子问题只求解一次并存储,避免重复计算。
  • optimal substructure(子问题的解可以用于求最终解):例如,最短路径Shortest Path问题,如果x在u->v的SP上,则SP[u->v]=SP[u->x]+SP[x->v],应用:Floyd,Bellman-Ford

思想:

  1. 将问题化为子问题
  2. 找到子问题的最优解
  3. 存储子问题的结果(表或者备忘录)
  4. 使用子问题的结果,以防止子问题被多次计算
  5. 最后计算问题的最终解。

步骤:

  1. 确定状态:根据最后一步的情况,化成子问题
  2. 转移方程:求最小最大计数等
  3. 初始条件和边界情况
  4. 确定计算顺序

解决模式:

Memoization(备忘录):Top Down : 递归+备忘录,自顶向下,记录计算过的值,触底返回

Tabulation(表):Bottom Up:循环+表,自底向上,从dp[0]一直填充到dp[n],按顺序填充表,并直接从已知状态计算未知,因此称为制表法。

**例子:**比如求解斐波拉契数列,采用动态规划可以把时间复杂度从指数级别降到多项式级别。采用动态规划,时间复杂度为O(n). Memoization图解Tabulation图解

// 采用递归,时间复杂度T(n)=2*T(n-1)+1,O(2^n),指数级
int fib_recur(int n){
	if(n<=1) return n;
	return fib(n-2)+fib(n-1);
}

// Memoization:递归+备忘录 
vector<int> memo(n+1,0);
int fib_Memo(int n){
    if(memo[n]==0){
        if(n<=1) memo[n]=n;
        else memo[n]=fib_Memo(n-1)+fib(n-1);
    }
    return memo[n];
}

// Tabulation : 循环+表
int fib_Tabul(int n){
	vector<int> F(n+1,0);
	int F[0]=0,F[1]=1;
	for(int i=2;i<=n;i++){
		F[i]=F[i-2]+F[i-1];
	}
	return F[n];
}
最长增长子序列问题

Longest Increasing Subsequence:找到序列中的最长增长序列的长度

例如:Given sequence A={10,22,9,33,21,50,41,60}

可以得到Increasing Subsequence={10},{9,33,41},{33,41,60},{33,50,60},{41},等

而 LIS = {10,22,33,50,60},{10,22,33,41,60},算法返回5

思路:

  1. 创建一个数组L[],用于存放每个元素终止的LIS长度。max(L[])为结果

  2. 假设最后一步将Ak放入LIS,则

    ​ 若存在 A [ k ] > A [ i ] A[k]>A[i] A[k]>A[i],则 L [ k ] = m a x ( L [ i ] ) + 1 L[k]=max(L[i])+1 L[k]=max(L[i])+1

    ​ 若对于所有的 i < k i<k i<k,有 A [ k ] < = A [ i ] A[k]<=A[i] A[k]<=A[i] L [ k ] = 1 L[k]=1 L[k]=1

最长公共子串问题

Longest Common Subsequence: 给定两个序列,找到二者的最长公共子序列。

例如:A=“abcdefg”,B=“abxdfg”,则LCS=“abdfg”

例如求 A[0…m]=“AGGTAB”,B[0…n]=“GXTXAYB”

思路:

  1. 创建一个数组L用于存放LCS
  2. 转移方程:最后一个元素两种可能,相同或者不同
  • A [ m − 1 ] = = B [ n − 1 ] A[m-1]==B[n-1] A[m1]==B[n1] L ( A [ 0... m − 1 ] , B [ 0... n − 1 ] ) = 1 + L ( A [ 0.. m − 2 ] , Y [ 0.. n − 2 ] ) L(A[0...m-1],B[0...n-1])=1 + L(A [0..m-2],Y [0..n-2]) L(A[0...m1],B[0...n1])=1+L(A[0..m2]Y[0..n2]
  • 否则 L ( A [ 0... m − 1 ] , B [ 0... n − 1 ] ) = m a x ( L ( A [ 0.. m − 2 ] , Y [ 0.. n − 1 ] ) , L ( A [ 0.. m − 2 ] , Y [ 0.. n − 1 ] ) ) L(A[0...m-1],B[0...n-1])=max(L(A [0..m-2],Y [0..n-1]),L(A [0..m-2],Y [0..n-1])) L(A[0...m1],B[0...n1])=max(L(A[0..m2],Y[0..n1])L(A[0..m2],Y[0..n1])
  1. 边界条件,若下标<0,就返回0
编辑距离问题

编辑距离:给定两个字符串A[m],B[n],有插入、删除、更改三种操作。计算将A转换为B所需要的最小操作次数。

例如:A =“ geek”,B=“ gesek”,1次,插入s即可

A=“ cat”,B =“ cut”,1次,更改a为u即可

A=“ sunday”,B =“ saturday”,3次,将un转为atur需将n换入r,插入t,插入a

递归思路:

考虑最后两个元素A[m-1],B[n-1]是否相同

  • 若相同,则忽略最后一个字符,问题缩小一个长度
  • 否则,计算三种此操作的代价,并选取最小+1
    • 插入:计算A[m],B[n-1]
    • 删除:计算A[m-1],B[n]
    • 修改:计算A[m-1],B[n-1]
  • 边界:m=0,返回n;若n=0,返回m

优化时间复杂度:建个矩阵(m+1)*(n+1)来记录操作代价,防止递归重复计算

优化空间复杂度:实际每次计算只需要用记录矩阵的一行,只需创一个2*(m+1)的数组即可,+1是为了考虑空字符串。

最低代价路径

Min Cost Path:给定代价矩阵cost[] []且均为正整数,矩阵中的位置(m,n)。要求从(0,0)到(m,n)的最小成本路径的成本。可以往右走,也可以往下走,也可以往下的对角线走。

思路:minCost(m, n) = min (minCost(m-1, n-1), minCost(m-1, n), minCost(m, n-1)) + cost[m] [n]。创建一个二维数据,记录结果即可。

硬币问题

第一种硬币问题说明

coins面额 S = { S1, S2, … , Sm} ,数量无限,取总面值为W,共有几种方式?

例:coins面额 S={1,2,3},数量无限,取总面额 W=5,则(1,1,1,1,1),(1,1,1,2),(1,1,3),(1,2,2),(2,3),共5种

例:coins面额={2,3,5,10},数量无限,取总面额w=15有几种可能?

思路:

分成两种情况:

  1. 不包含第Sm个硬币
  2. 至少包含1个Sm的硬币

则count(S[],m,n) = count(S[],m-1,W)+count(S[],m,W-Sm)

创建(W+1)*m的零矩阵,用于存储面额

第二种硬币问题:coins面额={2,5,7},取总面额为27,找出可凑得总面额的硬币数量最小情况。

思路:

假设最后一个硬币面额是A,则 f ( 27 ) = f ( 27 − A ) + 1 f(27)=f(27-A)+1 f(27)=f(27A)+1,但是最后一个硬币的面额有三种可能,A={2,5,7},所以 f ( 27 ) = m i n { f ( 27 − 2 ) + 1 , f ( 27 − 5 ) + 1 , f ( 27 − 7 ) + 1 } f(27)=min\{ f(27-2)+1,f(27-5)+1,f(27-7)+1 \} f(27)=min{f(272)+1,f(275)+1,f(277)+1},因此写作一般情况则为

f ( X ) = m i n { f ( X − 2 ) + 1 , f ( X − 5 ) + 1 , f ( X − 7 ) + 1 } f(X)=min\{f(X-2)+1,f(X-5)+1,f(X-7)+1\} f(X)=min{f(X2)+1,f(X5)+1,f(X7)+1}

然后要处理初始条件和边界情况:

  • 边界情况:若X-2,X-5,X-7<0,则数组越界,直接返回无穷。例f[1]=无穷
  • 初始值:f[0]=0
机器人走路问题

m*n个格子,机器人只能往下或者往右走,则从左上角走到右下角有几种走法?

思路:

  1. 确定状态:

    ​ 根据最后一步的情况,机器人只可能从A(m-2,n-1)或者B(m-1,n-2)两个位置走到终点。假设有X种方式走到A(m-2,n-1),Y种方式走到B(m-1,n-2),则共有X+Y种走法。

  2. 化成子问题,

    ​ 机器人有几种方式从左上角走到A,有几种方式走到B。这么一来格子的大小就缩小了一行,或者一列。子问题重叠,可用DP解。

  3. 转移方程:

    ​ $ f(m,n)=f(m-1,n)+f(m,n-1)$

  4. 初始条件和边界情况

    ​ 初始条件:f(0,0)=1

    ​ 边界情况:若m=0, f(m,n)=1;第一行只能从右边走过来

    ​ 若n=0, f(m,n)=1;第一列只能从上边走下来

青蛙跳问题

有n块石头,分别在x轴的0,1,…,n-1位置。一青蛙要从0跳到n-1,在i上最多可以往右跳ai。问青蛙能否跳到n-1。例子a=[2,3,1,1,4] 能,a=[3,2,1,0,4] 不能。

  1. 确定状态

    根据最后一跳的情况 k < n − 1 & & k + a [ k ] > = n − 1 k<n-1 \&\& k+a[k]>=n-1 k<n1&&k+a[k]>=n1

  2. 化成子问题

    青蛙能否跳到石头k,子问题重叠,可dp

  3. 转移方程

    找到所有能满足条件的k,只要有一个能跳到

  4. 初始条件和边界情况

    初始条件:f[0]=true

    边界情况:可以不考虑

动态规划题的题型判断

  1. 计数:有多少种方式可以实现结果
  2. 求最大最小值:
  3. 求存在性

参考资料

  1. https://zhuanlan.zhihu.com/p/78220312
  2. https://www.youtube.com/watch?v=lVR2u9lsxl8&list=PLdo5W4Nhv31aBrJE1WS4MR9LRfbmZrAQu
  3. https://www.bilibili.com/video/av45990457?from=search&seid=6213316788932313952
  4. 看蓝色链接点开即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值