Day4-part1
Coin Change Problems 零钱找零问题
问题描述
你有一些硬币(硬币区分面值:1,2,5,10),你要用这些硬币组成你的找零数额,你所面对的事情呢有两个。
- 怎么确定找零所需的最少硬币数
- 共有多少种可能的硬币组合
coin = [ 1,2,5,10 ]
value=7 → min_number=2 combinations={1,1,1,1,1,1,1} ; {2,5} ; {1,1,5} ; {2,2,1,1,1} ; {2,2,2,1};{2,1,1,1,1,1}
总共有好多好多种,我们要确定一个算法来实现
找零问题解决思路
[1 ] 确定dp数组(dp table)以及下标的含义
[2 ] 确定递推公式
[3 ] dp数组如何初始化
[4 ] 确定遍历顺序
[5 ] 举例推导dp数组
我们先把问题拆分,从获得最少的硬币数开始解决这个问题
1.确定dp数组
在昨天的学习中,我们知道了可以通过一维数组解决动态规划问题。在这里我们也设置dp数组为一维数组,同时dp数组存储的内容 dp[ i ] 应该意味着此时所对应的最小硬币数量。具体创建之后长这样:
2.确定递推公式
如果coin[ i ]==j ,那就是说明,刚好可以用一个硬币coin[ i ]来完成找零任务。dp[ j ]=1
如果coin[ i ]!= j ,那就是说明还需要别的零钱共同完成找零任务。
dp[ j ]=min(dp[ j ],dp[ j-coin[ i ] ]+1) 在上一个方案基础上加一枚coin[ i ]硬币
3.数组初始化
在之前我们求最大值的时候,是让数组初始值设为0,便于我们去寻找最大值。这里我们让数组初始值设为最大值,便于我们去寻找最小值。
coin=[1,2,5]
def get_min_coin(weight):
dp=[float('inf')] * (weight+1)
dp[0]=0
for i in range(len(coin)):
for j in range(coin[i],weight+1):
dp[j]=min(dp[ j ],dp[ j-coin[i] ]+1)
return dp[weight]
尝试自己实现一下这段代码吧!
零钱组合问题
目的是找到最大的组合数,还是用动态规划的思路来解决这个题目。
1.确定dp数组
和上面的一样,同样采用一维数组,但是现在dp数组中存放的信息是:最大零钱组合数
2.确定递推公式
dp[ j ]=dp[ j-coin[i] ]+dp[ j ] 这里说的是什么事情呢?
先看黄色的部分
看第二行:这里是不是对应没有考虑硬币6的情况下,weight=6的话最多的组合方法。
当考虑硬币6之后,看第三行。现在的可能组合数就变成了:没有考虑6的组合数+配合6出现的可能情况 也就是dp[6]=dp[6]+dp[6-6]=1+2=3
再看蓝色的部分,这时候你很容易就知道了dp[7]=dp[7]+dp[1]=1
dp[ j ]=dp[ j-coin[i] ]+dp[ j ] 这个公式十分显然了吧!
3.dp数组初始化
想求最大,那么理应设置为全0!但是我们需要对dp[ 0 ]赋值,认为0这种情况也是一种有效组合,不然你想想(如果dp[ 0 ]=0,整个递推还有意义吗?) dp[ 0 ]=1
def get_max_combination(weight):
dp=[0]*(weight+1)
dp[0]=1
for i in range(len(coin)):
for j in range(coin[i],weight+1):
dp[j]+=dp[j-coin[i]]
return dp[weight]
教材中采用二维数组实现,我们采用一维数组实现,也不失为一桩好事。
动手试试吧!
Day4-part2
Longest Increasing Subsequence 最长递增子序列
问题描述
给你一个序列,比如 5,12,2,7,1,8,13,12,获得最长子序列长度
5,7,8,13 len=4
2,7,8,13 len=4
采用动态规划解决这个题目
解决思路
[1 ] 确定dp数组(dp table)以及下标的含义
[2 ] 确定递推公式
[3 ] dp数组如何初始化
[4 ] 确定遍历顺序
[5 ] 举例推导dp数组
1.确定dp数组
同样首先采用一维数组解决这个问题,dp[ i ]代表的是当前点(其作为递增子序列的最后一个值)所包含的最长子序列的长度。
根据目前分析的定义,我们同时可以对这个数组进行初始化。每个点对应的最长子序列长度至少包含他本身的长度,也就是1,所以在这里进行赋值操作。
s=[5,12,2,7,1,8,13,12]
dp=[1]*len(s)
2.确定递推公式
针对5分析,显然dp[ 0 ]=1
针对5 12分析,5<12 dp[ 1 ]=max(dp[ 1 ],dp[ 0 ]+1)
针对 5 12 2分析, 显然不递增 dp[ 2 ]=1
针对 5 12 2 7 分析,
5<7 dp[ 3 ]=max(dp[ 3 ],dp[ 0 ]+1)=2
2<7 dp[ 3 ]=max(dp[ 3 ],dp[ 2 ]+1)=2
自己进行一下推导,是不是有:
if a[ i ]<a[ j ] dp[ j ]=max(dp[ j ],dp[ i ]+1) 感受一下整体过程吧!自己画一下,动动手!
s=[5,12,2,7,1,8,13,12]
def get_subsequence(s):
dp=[1]*len(s)
for i in range(0,len(s)):
for j in range(i,len(s)):
if s[j]>s[i]:
dp[j]=max(dp[j],dp[i]+1)
return dp
print(get_subsequence(s))
最终得到了整个数组,每个元素作为端点值对应的最长子序列长度。
Day4-part3
Edit Distance 编辑字符串
问题描述
给定一个源字符串和一个目标字符串,把源字符串怎么修改才能得到目标字符串?
字符串的操作包括:insert(插入),delete(删除),replace(替换)
s_source=banana
s_target=anaconda
肯定要进行若干次插入、删除、替换,我们想寻求最少的次数。
解题思路
[1 ] 确定dp数组(dp table)以及下标的含义
[2 ] 确定递推公式
[3 ] dp数组如何初始化
[4 ] 确定遍历顺序
[5 ] 举例推导dp数组
1.确定dp数组
在这里参照Day3-part1的思路,针对两个字符串还是采用二维数组才比较直观可靠。我们来确定一下dp[ i ][ j ]究竟代表的是什么呢?
dp[ i ][ j ]代表着操作数,从行的角度来看,我们关注第一行,第一行的意思是针对字符“”
,需要dp[ 0 ][ i ]个操作来实现s_target[ i ],具体填充方式参照下图。
从列的角度来看,第一列意味着针对字符“”,需要dp[ j ][ 0 ]个操作来实现s_source[ i ],具体填充方式参照下图。
分析的同时,我们也对dp数组完成了初始化!
2. 确定递推公式
去看一下day3的part1!
if s_source[ i ]== s_target[ j ] 那么 dp[ i ][ j ]=dp[ i-1 ][ j-1 ],什么意思呢,为什么呢。其实这里就代表着如果两个对应的字符相等的话,那么其实就不需要额外的操作了,直接继承两者未考虑前的dp值就好了!
if s_source[ i ]!= s_target[ j ] , 这种情况下肯定是要进行一次操作的,千真万确的,但是是在什么基础上进行这一次操作的呢?
dp[ i ][ j ]=1+min(dp[ i-1 ][ j-1],dp[ i ][ j-1 ],dp[ i-1 ][ j ]) 其实这一次添加的操作应该是在其邻接的三个状态之后完成,毕竟邻接三个状态都可以,仅通过一次操作实现当前状态。并且我们想知道总体的最少操作数,因此三者取最小值就好啦!
自己动手画一下,看看能不能实现这张图对应的效果呢!
s_source='banana'
s_target='ananconda'
def get_min_operation(s_source,s_target):
dp=[[0 for i in range(len(s_target)+1)]] for j in range(len(s_source)+1)
for i in range(len(s_target)+1):
dp[0][i]=i
for i in range(len(s_source)+1):
dp[i][0]=i
for i in range(1,len(s_source)+1):
for j in range(1,len(s_target)+1):
if s_source[i-1]==s_target[j-1]:
dp[i][j]=dp[i-1][j-1]
else:
dp[i][j]=1+min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])
return dp[len(s_source)][len(s_target)]
动手实现一下,实现好了就会了!
结语
在第四天的学习中,我们完成了三道题目的学习,分别对应动态规划的不同题目类型。
相信你已经学会了,你是最棒的哦!