【精品计划1】动态规划入门到熟悉,看不懂来打我啊

读到这里,你们应该能很快想到,依旧是斐波那契式递归啊。

对于n>=3:怎么能覆盖到三?

只有两种办法,从n-1的地方竖着放了一块,或者从n-2的位置横着放了两块

例4:给定一个由0-9组成的字符串,1可以转化成A,2可以转化成B。依此类推。。25可以转化成Y,26可以转化成z,给一个字符串,返回能转化的字母串的有几种?

比如:123,可以转化成

1 、2 、3变成ABC,

12 、3变成LC,

1 、23变成AW

三种,返回三,

比如99999,就一种:iiiii,返回一。

分析:求i位置及之前字符能转化多少种。

两种转化方法

1)字符i自己转换成自己对应的字母

2)和前面那个数组成两位数,然后转换成对应的字母

假设遍历到i位置,判断i-1位置和i位置组成的两位数是否大于26,大于就没有第二种方法,f(i)=f(i-1),如果小于26, f(i)=f(i-1)+f(i-2)

2.2矩阵系列问题


例5:给一个由数字组成的矩阵,初始在左上角,要求每次只能向下或向右移动,路径和就是经过的数字全部加起来,求可能的最小路径和。

1  3  5  9

8  1  3  4

5  0  6  1

8  8  4  0

路径:1 3 1 0 6 1 0路径和最小,返回12

分析:我们可以像之前一样,暴力的把每一种情况都试一次,但是依旧会造成过多的重复计算,以本题为例子最后解释一下暴力慢在哪里,以后不再叙述了。

比如本题来讲,我们尝试如下路径:

有很多路是重复走过的一遍。

再进一步说:

从1到6位置,有很多路可以走,直观感受一下:

所有路中,一定会有和最小的,但是我们并不知道,每次尝试一次1->6->终点的路线时,我们把所有的情况都算了一遍,这过程中我们浪费了相当多的有效信息。

这就是暴力的结果。

优化做法:生成和矩阵相同大小的二维表,用来记录到起点每个位置的最小路径和

接下来带着大家真正进入动态规划;

第一步:初始化(对于本题来说,第一列和第一行,我们别无选择,就一条路,因此,我们可以直接确定答案)

第二步:确定其余位置如何推出(我们称为状态转移方程)

直观来说,每个位置只可能是从上面,或者左边走来的:

对于普遍的位置i,j,只有i-1,j和i,j-1这两个位置可以一步走到这里,所以

DP[i,j]=min(DP[i,j-1],DP[i-1,j])+L[i,j](之前的最优解加上本位置的数字)

继续优化:和之前一样,这个式子实际上也是严格依赖两个值,一个是左边的值,一个是上面的值,所以,我们按之前的思路,应该可以想到可以压缩空间。

我们尝试用一维的空间来解题:

想象这是我们的第一行答案:

我们如何利用仅有的一维空间来更新出下一行呢?

我们要想:

  • 我们需要左面的数字,所以,本位置的左边必须是更新过的数字(否则就是左上的位置了),所以应该从左往右更新。

  • 我们需要上面的数字,这个不需要更新,本来就需要本位置的旧数字。

本题第二行为:8,1,3,4

第一行答案为

依次更新:

更新A:

(只能向下走)

更新B:

(比较从左边来和从上面来哪里比较小)

更新C:

更新D:

最后我们可以发现,伪代码是这样的:

For i  0 -> 高度:

For j  0 -> 宽度

DP[j]=min(DP[j-1],DP[j])+L[i,j]

时间不变,空间优化到o(min(高,宽))

例6:给一个由数字组成的矩阵,初始在左上角,要求每次只能向下或向右移动,路径和就是经过的数字全部加起来,求可能的最大路径和。

和例5只差一个“大”字,请自己思考

例7:一个矩阵,初始在左上角,要求每次只能向下或向右移动,求到终点的方法数。

和例5,6类似,只是方法数应该等于,左边的方法数加上上面的方法数

第二章末练习

1

一个只包含’A’、‘B’和’C’的字符串,如果存在某一段长度为3的连续子串中恰好’A’、'B’和’C’各有一个,那么这个字符串就是纯净的,否则这个字符串就是暗黑的。例如:

BAACAACCBAAA 连续子串"CBA"中包含了’A’,‘B’,'C’各一个,所以是纯净的字符串

AABBCCAABB 不存在一个长度为3的连续子串包含’A’,‘B’,‘C’,所以是暗黑的字符串

你的任务就是计算出长度为n的字符串(只包含’A’、‘B’和’C’),有多少个是暗黑的字符串。(网易17校招原题)

2、X国的一段古城墙的顶端可以看成 2*N个格子组成的矩形(如下图所示),现需要把这些格子刷上保护漆。

https://img-blog.csdn.net/20180506110603122

你可以从任意一个格子刷起,刷完一格,可以移动到和它相邻的格子(对角相邻也算数),但不能移动到较远的格子(因为油漆未干不能踩!)

比如:a d b c e f 就是合格的刷漆顺序。

c e f d a b 是另一种合适的方案。

当已知 N 时,求总的方案数。当N较大时,结果会迅速增大,请把结果对 1000000007 (十亿零七) 取模。

3.1 01背包


入门了动态规划之后,我们来看一个经典系列问题:背包问题

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:

f[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。则其状态转移方程为:

“将前i件物品放入容量为j的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i−1件物品的问题。

如果不放第i件物品,那么问题就转化为“前i−1件物品放入容量为j的背包中”,价值为f[i−1][j];

如果放第i件物品,那么问题就转化为“前i−1件物品放入剩下的容量为j−c[i]的背包中”,此时能获得的最大价值就是f[i−1][j−w[i]],再加上通过放入第i件物品获得的价值v[i]。

因此得出上面的式子。

继续优化空间(利用之前提到的知识):

如果我们压缩到一维空间解题,这次我们需要的是上面的位置和左上的位置,也就是说,我们需要左边的位置是没被更新过的,得出更新顺序应该从右往左:

​for i in range(1,n+1):

for j in range(v,-1,-1)

f[j] = max(f[j], f[j - w[i]] + v[i]);

3.2 完全背包


这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,很容易得出:

这跟01背包问题一样有O(VN)个状态需要求解,但求解每个状态的时间已经不是常数了

而是,总的复杂度可以认为是,将01背包问题的基本思路加以改进,得到了这样一个清晰的方法。这说明01背包问题的方程的确是很重要,可以推及其它类型的背包问题。但我们还是试图改进这个复杂度。

我们可以知道,对于一个普遍位置w,当前物品代价为2的话,下图中红色区域就是和位置w的取值相关的一些数值:

对当前物品的决策就依次是:不拿、拿一个、拿两个、拿三个(对应上面式子中的k)

我们算法优化的思路就是不断去除重复计算,显然我们可以继续优化这个式子。

请思考:我们的E3位置是如何得出的?其实是根据三个红色区域得出的,但是我们算位置w时又算了一遍,显然是重复了。而E3其实包含了不拿、拿一个、拿两个这些情况中的最优解,我们算w时直接用就可以了。

给出模板代码:

for (int i = 1; i <= n; i++)

for (int j = w[i]; j <= V; j++)

f[j] = max(f[j], f[j - w[i]] + v[i]);

对比两种背包:

这个代码与01背包的代码只有j的循环次序不同而已。为什么这样一改就可行呢?

首先想想为什么01背包中要按照j=V…0 j=V…0j=V…0的逆序来循环。这是因为要保证第i次循环中的状态f[i][j]是由状态f[i−1][j−w[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i−1][j−w[i]]。

而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i ii种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][j−w[i]],所以就可以并且必须采用j=0…V j=0…Vj=0…V的顺序循环。这就是这个简单的程序为何成立的道理。

最终给出状态转移方程给不明白的同学看:

(也可以通过数学导出此式)

3.3多重背包


和之前的背包不同,每种物品不是只有一件,也不是有无限件,这次的每种物品的数量都是有限制的,我们对于每种物品,可以选择拿一件、两件……p[i]件。

我们借用上一种问题的图:

看起来是类似的,位置w依旧和红色区域相关,但是我们可以直接根据E3来求出位置w吗?是不能的,因为条件变了,每种物品不是无限的,可能在w位置,图中椭圆圈出的位置代表着需要拿三个,但是如果规定最多拿两个,我们这种算法就出问题了。

一种做题思路:把每个物品都按01背包做:比如第i种物品,我们就按有p[i]件相同的物品。每一种物品都是如此,按01背包做就可以了。(但是显然很蠢)

改进:

我们平时买东西时,难道带的全是一元的硬币吗?当然不是,只要手中的钱可以凑出商品的价格即可,比如9元的东西,我不一定用九个硬币(背包问题的物品)来付钱,可以5元+4个1元。

背包问题也一样,我们不一定要全部拆成1的物品,只要我们的物品可以代表0——>p[i]的所有情况,我们就认为这种策略是正确的。

那如何拆p[i]个物品可以保证我们的物品可以代表0——>p[i]的所有情况呢?这里要借助2进制思想。

一个n位的二进制数可以取0到2的n次方-1,第i位代表的是2的i-1次方。

对应到物品:

我们的p[i]=15,我们怎样拆呢?

1+2+4+8即可,这四个数一定可以组合出0-15的任何一个数。

二进制拆分代码如下:

for (int i = 1; i <= n; i++) {

int num = min(p[i], V / w[i]);

for (int k = 1; num > 0; k <<= 1) {

if (k > num) k = num;

num -= k;

for (int j = V; j >= w[i] * k; j–)

f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);

}

}

3.4 一些变形选讲


1)最常见的一些变形,甚至不能说是变形,上面也提到过,但是怕同学们不知道:

我们常见的问题中,一般是问最优解,可能是最大,或者最小,但是,问题也可能是方法的数量,这个时候,一般把状态转移方程中的max(min)改为sum(求和)即可,当然,压缩空间后的样子还是需要自己写。

2)初始化的细节问题

我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求"恰好装满背包"时的最优解,有的题目则并没有要求必须把背包装满。这两种问法的区别是在初始化的时候有所不同。

如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1…V]均设为−∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。

如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0…V]全部设为0。

为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing “恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是

−∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

3)常数优化

前面的代码中有for(j=V…w[i]),还可以将这个循环的下限进行改进。

由于只需要最后f[j]的值,倒推前一个物品,其实只要知道f[j−w[n]]即可。以此类推,对以第j个背包,其实只需要知道到f[j−sumw[j…n]]即可,代码自行修改。

4)其实拆解二进制物品并不是多重背包的最优解,但是最优的单调队列思想写起来有些繁琐,可能以后会写。

可以刷的题

=====

鉴于有一些同学说简单,我把去年写的一些题解放在这里:

背包是否装满

单调栈

单调双端队列

双端队列优化的背包问题

字符串上的动态规划

皇后问题(位运算)

旅行商问题(认识状态压缩)

蓝桥杯 摔手机 费时巨多的题解

2018hbcpc dp总结

HDU1029 HDU1087 HDU1176 HDU1257 POJ1458

POJ2533 HDU1114 HDU1260 HDU1160

HDU1069 POJ3616 POJ1088

POJ1189 UVA12511 HDU2845 HBCPC2018 K

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后希望可以帮助到大家!

千千万万要记得:多刷题!!多刷题!!

之前算法是我的硬伤,后面硬啃了好长一段时间才补回来,算法才是程序员的灵魂!!!!

篇幅有限,以下只能截图分享部分的资源!!

(1)多线程(这里以多线程为代表,其实整理了一本JAVA核心架构笔记集)

image

(2)刷的算法题(还有左神的算法笔记)

image

(3)面经+真题解析+对应的相关笔记(很全面)

image

(4)视频学习(部分)

ps:当你觉得学不进或者累了的时候,视频是个不错的选择

在这里,最后只一句话:祝大家offer拿到手软!!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后希望可以帮助到大家!

千千万万要记得:多刷题!!多刷题!!

之前算法是我的硬伤,后面硬啃了好长一段时间才补回来,算法才是程序员的灵魂!!!!

篇幅有限,以下只能截图分享部分的资源!!

(1)多线程(这里以多线程为代表,其实整理了一本JAVA核心架构笔记集)

[外链图片转存中…(img-yF2IgB2H-1713553070562)]

(2)刷的算法题(还有左神的算法笔记)

[外链图片转存中…(img-TQ98ffEU-1713553070563)]

(3)面经+真题解析+对应的相关笔记(很全面)

[外链图片转存中…(img-BvowwKC4-1713553070568)]

(4)视频学习(部分)

ps:当你觉得学不进或者累了的时候,视频是个不错的选择

在这里,最后只一句话:祝大家offer拿到手软!!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值