这次比赛的题目难度又降低不少,基本上都是出现过的经典老题,最后一题更是曾多次出现在CSDN的每日一练中,看来官方是想提醒大家多多参与每日一练啊。
第一题:小艺读书
书是人类进步的阶梯。 小艺每周因为工作的原因会选择性的每天多读几页或者少读几页。
小艺想知道一本n页的书她会在周几读完。
分析
一开始以为是老题——给定n页的书,每天读3页或4页,然后想在某一天读完,有多少种读法。仔细一读,发现变得更简单了:题目给出了一个包含7个元素的列表,表示每天读多少页。然后题目就可以通过不断用总页数 n 减去每天读书的页数,直到 n 等于或小于0为止,即可得到需要读多少天。
唯一需要注意的是当读书的天数超过一周时,结果大于等于7,需要重新变成0开始。这也很简单,只要对7取余即可。该题简单到连结果都不需要转化为星期几,直接返回0到6的数字即可。
参考代码
def solution(n, pages):
result = 0
while n>0:
n -= pages[result%7]
result += 1
return result%7
第二题:鬼画符门之宗门大比拼
给定整数序列A。求在整数序列A中连续权值最大的子序列的权值。
分析
题目标题起得很唬人,其实也是经典老题:求整数序列中连续子序列之和的最大值,LC原题。前几天在CSDN问答频道也有人问过,问哥还回答过,可惜没有被采纳。
解法有很多种,暴力解法也可,就是把所有的子序列求出来,然后比较它们的和,返回最大值。不过这种解法有可能因为耗时太长而无法通过测试,问哥也没有尝试。
时间复杂度为O(n)的解法是使用贪心算法或动态规划。算法的核心思想是:对序列中每一个整数而言,如果之前的序列之和是负数,就不相加。因为如果加上之前的序列,反而比自己本身的值还要小,而我们要找最大值,显然不符合,所以最大子序列应该从自己开始算起。
所以,额外定义两个变量,一个用来记录连续相加的子序列之和,一个用来记录最大的子序列之和。使用 max() 函数即可完成比较。当for循环遍历完一遍列表,即可返回结果。
唯一要注意的一点是,两个变量在初始化的时候,不需要表示“极小值”,而是直接赋值列表的第一个元素即可。这样可以假定列表只有一个元素,那么最大子序列当然就是第一个元素。然后遍历的时候从第二个元素开始比较。
参考代码
def solution(n, arr):
result = temp = arr[0]
for i in arr[1:]:
temp = max(i, i+temp)
result = max(result, temp)
return result
第三题:硬币划分
有1分,2分,5分,10分四种硬币,每种硬币数量无限,给定n分钱(n<=100000),有多少中组合可以组成n分钱?
分析
四道题里稍微有点难度的,但也是老题。首先常识告诉我们,用暴力的穷举法一定能做出来。无非是四层嵌套循环abcd,分别表示1分、2分、5分、10分四种硬币,然后当下面等式成立的时候,结果加一:
时间复杂度为 。因为这种方法太直观了,问哥一上来就试过,果不其然,无法通过。
该使用万能的动态规划了(此题其实属于动态规划中的完全背包类型)。
这里需要在逻辑上稍微理一理。我们要使用1、2、5、10分四种硬币得到n,那这个可能的方法数量可以由什么(状态)推导而来呢?我们用f(n)表示使用四种硬币组成价值n的方法数,在可能的结果里先拿走一枚10分的硬币(假设n>=10),那么剩下的硬币总价值为n-10。相对应地,f(n-10)表示使用这四种硬币有多少种方法可以组合成 n-10。假设规定最后这10分的价值只允许使用10分的硬币,那很显然,f(n)=f(n-10)。但是这10分、乃至价值n都同样可以使用1、2、5硬币来得到,所以我们要加上如果只使用1、2、5分硬币组成n的方法数,用f'(n)表示的话可以得到以下公式:
不难发现,如果我们在f'(n)里拿走5分硬币,用f''(n)表示只使用1、2两种硬币得到n的方法数,同样可以推导出
为了使表达更清楚,我们更换一下f的表示方法,可得以下公式:
其中、、分别代表:只使用1分2分硬币、只使用1分2分5分硬币、使用全部4种硬币得到价值 n 的方法数。而,也就是只使用1分硬币的情况,方法数必然恒定是1,所以无须推导。
你可能会有一种“错觉”:这样会不会漏掉一些可能,比如那种“在前面使用了四种硬币,而在最后10分的时候只使用了1、2、5分硬币”?其实不然,这种情况必然已经包含在了公式的计算结果里,因为硬币是没有使用顺序的,你可以假设拿走的那一枚10分的硬币,是存在于“前面使用了四种硬币”里的某一枚(总的10分硬币的数量相同),这样,该情况就必然包含在了结果里。
根据上述推导,我们可以列出动态规划图表如下:
价值(n) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
只使用1分硬币() | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
只使用1分、2分硬币() | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 | 5 | 6 | 6 | 7 | 7 |
只使用1分、2分、5分硬币() | 1 | 1 | 2 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 13 | 14 |
使用全部四种硬币() | 1 | 1 | 2 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 |
最终我们要返回的结果就是图表的右下角数字,如果列出二维数组的话,也就是dp[3][n]。
参考代码
def solution(n):
result = [[1]*(n+1) for i in range(4)]
for i in range(2,n+1):
k = 1
for j in [2,5,10]:
if i>=j:
result[k][i] = result[k][i-j]+result[k-1][i]
else:
result[k][i] = result[k-1][i]
k += 1
return result[3][n]%1000000007
该题因为要考虑到传统编程语言可能会存在得出的结果太大,产生数据溢出的问题,从而要求得到的结果对1000000007取模。所以,在返回的时候要将得到的结果进行模运算。
第四题:饿龙咆哮-逃离城堡
小艺酱误入龙族结界,被恶龙带回城堡,小艺酱决定逃离城堡,逃离龙族结界.。
总路程为c, 小艺酱的速度是vp,饿龙速度为vd。饿龙会在t小时后发现小艺酱出逃。
小艺酱担心自己跑不出去,准备了好多珍宝。 每当饿龙追上自己的时候小艺酱就会丢下一个珍宝,饿龙捡到珍宝会返回自 己的城堡进行研究,研究f小时后,再出城堡追赶小艺。
小艺想知道自己至少需要丢多少珍宝才能让自己安全逃出结界。
分析
CSDN每日一练里的题目,也是数学里再熟悉不过的追赶题——速度快的追速度慢的,问两人(物)相遇的时候,速度快的跑了多少路。因为每次饿龙捡到珍宝后,都要退回城堡再出发,于是本题就可以转化为,小艺要丢几次珍宝,才使得最后一次饿龙追不上小艺。而每次追赶的时候,小艺都先跑了 t 时间的路,所以 vp*t 就是每次追赶时的初始距离,设 x 为饿龙追上小艺所花的时间,易得 ,而vd*x 则等于饿龙追上小艺时跑的路程,于是可得,当最后一次追赶 大于结界总路程 c 的时候,代表饿龙追不上,而小艺成功逃出结界。
参考代码
def solution(vp, vd, t, f, c):
result = 0
if vd<=vp: return 0
x = vp*t/(vd-vp)
while vd*x<c:
result += 1
t += 2*x+f
x = vp*t/(vd-vp)
return result
虽然看起来似乎是官方在号召大家多多参加CSDN的每日一练,但问哥不得不吐槽,每日一练中的题目真的是文字描述问题太多,常常让人看不懂,而且测试数据也存在问题。问哥已经亲身遇到过其他平台的真题在CSDN无法通过测试。只希望官方能够重视起来,尽快对在线答题系统进行优化吧。