动态规划拙见

动态规划

刷题也有好几个月了,但一直都是沉醉于数组和字符串这些入门的知识点,这假期准备好好啃一些知名的算法了
动态规划作为科技公司基本上必考的知识点,在刷题网站中占有很大的比重,很多的题目都可以用动态规划去做。今天就来捋一捋dynamic program。

什么是动态规划

先来个例题:
问总共有多少种方法可以走到右下角?问走到右下角有多少种方法

				如果想用动态规划解题,首先要先明确什么样类型的题目适合用动态规划去做,毕竟面试时
				候,面试官不可能说:来用动态规划给我解这道题。

动态规划题目特点

		**1**:计数
				-计算有多少种方法走到右下角
				-有多少种方法选出K个数使得和等于sum
		**2**:求最值
		 		-从左上角到右下角路径的最大数值和‘
		 		-最长上升子序列长度
		**3**:求存在性
				-取石子游戏,先手能否获胜
				-能否选出K个数使和为sum

实战例题(LeetCode)

	[LeetCode322](https://leetcode-cn.com/problems/coin-change/)

在这里插入图片描述
这就是一道典型的动态规划的题目,因为题目要求最少的硬币个数,但不是说所有的求最值都一定是动态规划。
题目要求用最少的硬币数,常规的思维也就是直觉一定是先尽量用较大的面额的硬币,剩下的再用第二大,以此类推这样一定是最少的。*但这样一定是对的吗?*来看个例子:

例如有2,5,7三种面额的硬币,请你凑出27元。
在这里插入图片描述
所以直觉不一定是对的。动态规划有固定的做题步奏。

一:确定状态
		动态规划中确定状态是最重要的事情,类似于确定数组中a[i]代表着什么意义。
		确定状态需要两个意识:
		    ---- 最后一步
		    ---- 子问题
		    
		**最后一步**:
				就像LeetCode322题目中的一样假设要凑的是用2,5,7凑27,虽然最开始不知道最优策略是
		啥,但是可以明确的是肯定是K枚硬币a1,a2............ak加一起和为27。
		所以一定有一枚最后的硬币ak,除去这个硬币,其他的加一块就是27-ak。

在这里插入图片描述
由上图可以得知:我们不关心前K-1枚硬币怎么拼出27-ak,可能有1种方法,也可能有1000000种,不care,但是可以确定的是 前面的确是拼出了27-ak
再者,因为是最优策略,所以前K-1枚硬币一定是最少的
接下来的问题就是怎么用最少的硬币拼出***27-ak***。
原问题是用最少的硬币拼出 27.,将问题转化为更小的规模了。为了简化定义可以用f(x) = 最少用多少枚硬币拼出x。
且最后一枚硬币ak只可能是2,5,7中的一枚硬币,如果ak=2,则 f(27)= f(27-2) + 1
,最后加一是加上最后一枚2块钱的硬币。其他的情况以此类推。
故可得出 f(27) = min { f(27-2)+1, f(27-5)+1, f(27-7)+1}
上式可理解成拼出27元所需的硬币数目等于(拼出25所需的数目加一,拼出22所需的数目加一,拼出20所需的数目加一,三个中的最小值。)

二:转移方程
		**f(27) = min { f(27-2)+1,  f(27-5)+1,  f(27-7)+1}**
		这就是实际code时候 ,也包括面试时候最重要的东西,写对转移方程基本上这题就做对一大半了。
三:边界条件
		刷过LeetCode或者牛客的人应该都有这种体验,一道题能不能通过往往都是取决于那些坑爹的边界条
件或者奇葩的测试用例。
		例如用2 3 5拼27那个例子,如果x-2 x-5 x-7小于0怎么办,什么时候停下来?
		所以f(1) = min { f(-1)+1,  f(-4)+1,  f(-6)+1} = 正无穷,表示拼不出来。
		设置初始条件:f[0] = 0
四:计算顺序
		现在已经知道了转移方程:f(X) = min { f(X-2)+1,  f(X-5)+1,  f(X-7)+1}
		初始条件:f[0] = 0
		计算顺序从小到大,先计算f[1],f[2].......f[27].

从图可以看出,从左往右计算,当计算拼出k的时候,需要分别用到前面的k减去前面三个面额硬币的数据。
在这里插入图片描述

小结*
		动态规划的流程
		1:确定状态
				-最后一步
				-子问题
		2:转移方程
		3:边界条件
				-数组不可越界
				-初始条件的设定‘
		4:计算顺序
				-从左往右还是相反

BB了那么多,那道LeetCode还是没做呢。322
talk is cheap,show me your code!

dp = [sys.maxsize-1] * (amount+1)#定义一个dp矩阵,分别存放着拼出dp[i]所需最少的硬币个数
        dp[0] = 0 #初始化dp矩阵最开始的元素为0
        for i in range(amount+1):#初始化之后开始最dp数组中每个元素进行赋值
            if dp[i] == sys.maxsize - 1:#如果dp[i]当前元素为无穷,则说明此时无解,进行下轮循环
                continue
            for coin in coins:#若此时dp[i]不是无穷大,则分别添加硬币列表中的硬币进行选择
                if coin + i <= amount:#如果加上此时的硬币还小于amount
                    dp[i+coin] = min(dp[coin+i],dp[i]+1)#比较原始的跟加上一枚之后哪个更小
        if dp[-1] == sys.maxsize -1:return -1#如果循环赋值之后,dp[i]最后一位还是无穷,则无解
        else:return dp[-1]#有解,则返回最后一个数

在这里插入图片描述

觉得代码注释的可能不太清楚,灵魂画手又手画了一幅图解释代码中的循环都在干啥,觉得起码这道题应该是解释清楚了

在这里插入图片描述在这里插入图片描述

还可以用递归去解这道题,用字典去存储每一步运算的结果这样可以省掉多次重复运算,应该也是可以的!

猜你喜欢

转载自blog.csdn.net/weixin_38389509/article/details/113062181