解决动态规划问题4步曲

  • 实际正确答案 —— 7+5+5+5+5=27,才用了5枚硬币。

所以这里贪心算法是并不适用。

题目中关键词“最少的”,这是用到“动态规划”的味道

解决动态规划问题4步


第一步,确定问题状态

动态规划问题求解需要先开一个数组,并确定数组的每个元素f[i]代表什么,这就是确定这个问题的状态。这类似于解数学题中,设定x,y,z代表什么。

A、确定状态首先提取“最后一步”

最优策略必定是K枚硬币a1, a2, …, aK面值加起来是27。

找出不影响最优策略的最后一个独立角色,这道问题中,那枚最后的硬币aK就是最后一步。把aK提取出来,硬币aK之前的所有硬币面值加总是27-aK。因为总体求最硬币数量最少策略,所以拼出27-aK的硬币数也一定最少(重要设定)。

在这里插入图片描述

B、转化子问题

最后一步aK提出来之后,我们只要求出“最少用多少枚硬币可以拼出27- aK”就可以了。

这种与原问题内核一致,但是规模变小的问题,叫做子问题

为简化定义,我们设状态f(X)=最少用多少枚硬币拼出总面值X

我们目前还不知道最后的硬币aK面额多少,但它的面额一定只可能是{2, 5, 7}之一。

  • 如果aK是2,f(27)应该是f(27-2) + 1(加上最后这一枚面值2的硬币)

  • 如果aK是5,f(27)应该是f(27-5) + 1(加上最后这一枚面值5的硬币)

  • 如果aK是7,f(27)应该是f(27-7) + 1(加上最后这一枚面值7的硬币)

除此以外,没有其他的可能了。至此,通过找到原问题最后一步,并将其转化为子问题。

为求面值总额27的最小的硬币组合数的状态就形成了,用以下函数表示:

f(27) = min{f(27-2)+1, f(27-5)+1, f(27-7)+1}

在这里插入图片描述

第二步,转移方程,把问题方程化

f[X] = min{f[X-2]+1, f[X-5]+1, f[X-7]+1}

动态规划都是要开数组,所以上面式子改用方括号表示。实际求解动态规划类问题,正确列出转移方程正确基本上就解决一半了。

递归的解法:

// f(X)返回最少用多少枚硬币拼出X

int f(int X) {

// 0元钱只要0枚硬币

if (X == 0) return 0;

// 初始化用无穷大(

int res = Integer.MAX_VALUE;

// 最后一枚硬币是2元

if (X >= 2) {

res = Math.min(f(X – 2) + 1, res);

}

// 最后一枚硬币是5元

if (X >= 5) {

res = Math.min(f(X – 5) + 1, res);

}

// 最后一枚硬币是7元

if (X >= 7) {

res = Math.min(f(X – 7) + 1, res);

}

return res;

}

执行图如下:

在这里插入图片描述

要算f(27),就要递归f(25)、f(22)、f(20),然后下边依次递归。

在这里插入图片描述

问题明显:重复递归太多

这是求f(27),等待时间还可以接受。如果求f(100)呢?

求总体最值,可优先考虑动态规划,不要贸然去递归。

第三步,按照实际逻辑设置边界情况和初始条件。

如果不按照实际逻辑设置边界情况和初始条件,即使转移方程正确也大概率无法跑通代码。

f[X] = min{f[X-2]+1, f[X-5]+1, f[X-7]+1}的边界情况是[x-2][x-5][x-7]不能小于0(硬币面值为正),也不能高于27。

故对边界情况设定如下

如果硬币面值不能组合出Y,就定义f[Y]=正无穷。例如,f[-1] = f[-2] = … = 正无穷f[1] = min{f[-1]+1, f[-4]+1,f[-6]+1} = 正无穷特殊情况:本题的F[0]对应的情况为F[-2]、F[-5]、F[-7],按照上文的边界情况设定结果是正无穷,但是实际上F[0]的结果是存在的(即使用0个硬币的情况下),F[0]=0

可是按照我们刚刚的设定,F[0]=F[0-2]+1= F[-2]+1=正无穷岂不是矛盾?这种用转移方程无法计算,但是又实际存在的情况,就必须通过手动定义。这里手动强制定义初始条件为:F[0]=0

而从0之后的数值是没矛盾的,比如F[1]= F[1-2]+1= F[-1]+1=正无穷(正无穷加任何数结果还是正无穷),F[2] = F[2-2]+1 = F[0]+1=1

第四步,确定计算顺序并计算求解

那么开始计算时,是从F[1]F[2]开始呢?还是从F[27]F[26]开始呢?

判断计算顺序正确与否的原则是:当我们要计算F[X](等式左边,如F[10])的时候,等式右边(f[X-2]f[X-5]f[X-7]等)都是已经得到结果的状态,这个计算顺序就是OK的。

实际就是从小到大的计算方式(偶有例外的情况)。

例如我们算到F[12]的时候,发现F[11]F[10]F[9]都已经算过了,这种算法就是对的。而开始算F[27]的时候,发现F[26]还没有算,这样的顺序就是错的。

很显然这样的情况下写一个for循环就够了。

回到这道题,采用动态规划的算法,每一步只尝试三种硬币,一共进行了27步。算法时间复杂度(即需要进行的步数)为27*3。

与递归相比,没有任何重复计算

本文的题目来源:LeetCode - Medium - 322. Coin Change

代码如下:

最后

看完美团、字节、腾讯这三家的面试问题,是不是感觉问的特别多,可能咱们又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。

开篇有提及我可是足足背下了1000道题目,多少还是有点用的呢,我看了下,上面这些问题大部分都能从我背的题里找到的,所以今天给大家分享一下互联网工程师必备的面试1000题

注意不论是我说的互联网面试1000题,还是后面提及的算法与数据结构、设计模式以及更多的Java学习笔记等,皆可分享给各位朋友

最新“美团+字节+腾讯”一二三面问题,挑战一下你能走到哪一面?

互联网工程师必备的面试1000题

而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题

最新“美团+字节+腾讯”一二三面问题,挑战一下你能走到哪一面?

面试1000题,还是后面提及的算法与数据结构、设计模式以及更多的Java学习笔记等,皆可分享给各位朋友

[外链图片转存中…(img-mvqBTS7P-1721198071514)]

互联网工程师必备的面试1000题

而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题

[外链图片转存中…(img-Wuz9Fj62-1721198071515)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值