请求出在 12345678 至 98765432 中,有多少个数中完全不包含 2023 。 完全不包含 2023 是指无论将这个数的哪些数位移除都不能得到 2023 。 例如 20322175,33220022 都完全不包含 2023,而 20230415,20193213 则 含有 2023 (后者取第 1, 2, 6, 8 个数位) 。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一 个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
代码及思路:
一定要申清题目,我当时做的时候头脑有点不清醒,想着不包含,写着写着就变成包含了。。。,5分就这样没了
暴力循环求解
ans = 0
def find(x):
tmp = "2023"
a = str(x)
j = 0
for i in a: # 对数字进行遍历
if i == tmp[j]: # 从左到右逐个遍历是否依次会出现2023四个数字
j += 1
if j == 4: # 说明完全包含2023
return True
return False
for i in range(12345678,98765433):
if not find(i): # 注意这里要的是完全不包含2023,
ans += 1 # 要取反加not,我这里没仔细看,结果就是反向答案
print(ans)
# print(98765433 -12345678 - 460725) # 用反向答案求答案。。。
# 反向答案 460725
# 答案 85959030
试题 B: 硬币兑换(5分)
代码及思路:
这道题在比赛时,我首先看了一眼没有思路就跳过去了,再后来要交卷了,想起来这个填空题还没有写,匆忙之下就想着2023有2023个,最后加一起的时候应该也是最多的,那么谁加谁等于2023,我很直接的想到了1011+1012,有1011个,然后加上2023个交卷了。事后在看这个突然发现答案与我擦肩而过,既然1011+1012=2023,那么1010+1013 = 1009+1014 = … = 1 + 2022,都满足等于2023,且个数依次就是从1加到1011个,最后再加上新的2023就是答案了???
不,这只是比较直接的想法,在已有的硬币中有1~2023,生成的硬币在这里面的话和为2023最后是最多的 ,但可以生成硬币值大于2023的新的硬币,这样就是另一种情况了。
根据两个硬币加和为新硬币值,可以发现最后加的一次为 (新硬币值-1) / 2,以2023为例,从1+2+…+1011, 最后加到了1011结束,类推之后,我们暂且把最后加和的新硬币值设为NewSum,加和的最后一个硬币值设为End,加和的起始硬币为Start,则有NewSum = End * 2 + 1,又因为新硬币值为Start+2023,有NewSum = Start + 2023,又因为最后的种数是以Start为首项,1为公差的等差数列的和,项数共有End - Start + 1项。
可以得到和为NewSum的种数为 Sn = (End - Start + 1) * (Start + End) / 2
结合NewSum = End * 2 + 1,NewSum = Start + 2023。
我们可以得到关于End的一个方程,
是一个开口向下的二次函数,化简内部关键部分得,
可知当End为 1348.5 时种数取得最大值,因为只能取整数,则End取1348和1349都行。
代入方程得Sn = 682425
ans = 2023 # 现有的2023个
for i in range(1,1012):
ans += i # 合成的2023的个数
print(ans) # 513589
ans = 0
# 当我们化简完式子就知道End取什么值了,不化简用代码来枚举也是同样的
for End in range(1012,2023): # 由公式知,end是变量,end使Sn最优
NewSum = End * 2 + 1 # 大于2023的新硬币值
Start = NewSum - 2023 # 起始的第一个数
Sn = (End - Start + 1) * (Start + End) // 2 # 数列求和公式
if Sn > ans:
ans = Sn
else:
print(ans) # 682425
break
试题 C: 松散子序列(10分)
代码及思路:
正确理解:根据pi-p(i-1)>=2,这里pi 是原字符串的下标,即在子串中两个相邻的字符在原字符串中的下标至少要相邻2.要获得子串的最大值,并且满足条件,可以使用递推,即子序列中后一位的最大值由前一位最大值加上本身的值得到。使用动态规划进行动态递推。创建dp数组,令 dp[i] 表示以第 i 个字符结尾的松散子序列中的最大价值,定义一个指针K来表示满足 pi − pk ≥ 2 的最大的位置,通过转移方程dp[i] = dp[k] + val[s[i]]更新最大值。
def getmax_value(s):
n = len(s)
# res = ""
dp = [0] * n # 创建dp数组,令 dp[i] 表示以第 i 个字符结尾的松散子序列中的最大价值
val = {chr(i+97): i+1 for i in range(26)} # 创建一个字母:值的字典,便于获取字母的值
# print(val)
k = -1 # 定义一个指针K来表示满足 pi − pk ≥ 2 的最大的位置,通过转移方程dp[i] = dp[k] + val[s[i]]更新最大值
for i in range(n):
if i == 0:
dp[i] = val[s[i]] # 把第一个字母的价值赋值
elif i == 1:
dp[i] = max(val[s[i]], dp[i-1]) # 保存最大值
else:
while k < i-2 or (k >= 0 and s[k+1:k+3] != s[i-1:i+1]): # k == i - 2跳出循环
k += 1 # k来记录满足pi-pk >= 2的最大位置,(逐个递增)
# res += s[k]
dp[i] = dp[k] + val[s[i]] # 递推,当前位置值最大值为前一个满足距离条件的最大值+现在的值
dp[i] = max(dp[i], dp[i-1]) # 现在的位置更新最大值,保证k位置最大值是k其前面的最大值,
# print(res)
return dp[-1] # 最后为整体最大值
s = input() # azaazaz
ans = getmax_value(s)
print(ans)
错误理解:t是原序列的子序列,就是其中任意一部分。然后看定义就是说松散子序列满足当前字母的价值减去前一个字母的价值要大于等于2.,然后就暴力循环判断,测试用例只有一个,看着答案对了,就没多想。。。
s = input() #"azaazaz"
ans = ""
res = 0
length = len(s)
for i in range(1,length):
if ord(s[i]) - ord(s[i-1]) >= 2: # 满足条件,说明是子序列中的一个
ans += s[i]
res += ord(s[i]) - 96 # 价值从1开始
# print(ans) # zzz
print(res) # 78
试题 D: 管道(10分)
代码及思路:
首先吐槽一下,为什么测试样例只给一个啊,我写出来,为了测试一下样例,把代码中的范围缩小了下,结果就是缩小了没有改回去,真正测试肯定不通过的(还是怪自己粗心,白忙活了。。。)
这道题要求管道中每一段都检测到有水流,然后求这个条件的最早时间,在由管道每一段它根据时间不同,它周围的某一区间管道也会检测到,根据这种与区间相关的性质,我们可以想到用差分数组和前缀和来进行操作区间。如下图,构造一个区间数组,初始状态为0 ,然后对每一次时间的值,根据给的区间公式 Li-(Ti-Si)~ Li+(Ti-Si)进行求出区间左边和右边,这就可以用到差分数组,在当前t时间时对每个阀门进行求区间,然后对区间加一,当最后dp数组中从起始位置1到管道最右边都为1时,说明每段都检测到了水流,输出时间t,退出循环。
这里时间T需要我们自己寻找,那么就有遍历查找和二分查找两种方法数据量太大,暴力是过不完的。
下面是暴力解的思路:
在此基础上加上二分可以降低遍历时间 t 的时间复杂度O(n)为O(logn)
n,len = map(int,input().split())
L,S = [],[]
for i in range(n):
a,b = map(int,input().split())
L.append(a)
S.append(b)
minS = min(S) # 直接让t从最小的Si开始,就能开始检测了
for t in range(minS,100000000): # 就是这里t的范围一定要大,。。
dp = [0] * (len+2) # 构造dp数组记录状态,我们定义数组管道长度区间中全为1时,t即为答案
# 构建差分数组
for i in range(n, 0, -1):
dp[i] -= dp[i - 1]
for i in range(n): # 求一个阀门的左右区间
if t >= S[i]:
a = L[i] - (t-S[i])
b = L[i] + (t-S[i])
if a < 0: # 最左边不能为负数
a = 0
if b > len: # 最右边为管道长度
b = len
# print(a,b)
# 转换加减(区间加减-->端点加减)
dp[a] += 1
dp[b+1] -= 1
# 前缀相加(前缀和公式)
for i in range(1,len+2):
dp[i] += dp[i-1]
# print(dp) # 查看dp数组
if sum(dp[1:len+1]) == len: # 区间全为1说明完成
print(t)
break
# 输入
3 10
1 1
6 5
10 2
# 查看dp数组
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0]
[1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0]
[1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
# ans
5
下面是加上二分进行优化后的代码:
我们就是要找一个时间 t ,满足 t 时刻刚好覆盖区间,用二分查找这样的时间 T 会比遍历快很多。
"""
3 10
1 1
6 5
10 2
"""
n,len = map(int,input().split())
L,S = [],[]
for i in range(n):
a,b = map(int,input().split())
L.append(a)
S.append(b)
def checkT(t): # 返回当前时间T的区间状态和
dp = [0] * (len+2) # 构造dp数组记录状态,我们定义数组管道长度区间中全为1时,t即为答案
# 构建差分数组
for i in range(n, 0, -1):
dp[i] -= dp[i - 1]
for i in range(n): # 求一个阀门的左右区间
if t >= S[i]:
a = L[i] - (t-S[i])
b = L[i] + (t-S[i])
if a < 0: # 最左边不能为负数
a = 0
if b > len: # 最右边为管道长度
b = len
# print(a,b)
# 转换加减(区间加减-->端点加减)
dp[a] += 1
dp[b+1] -= 1
# 前缀相加(前缀和公式)
for i in range(1,len+2):
dp[i] += dp[i-1]
# print(dp) # 查看dp数组
return sum(dp[1:len+1]) # 返回该时间的区间和
l,r = 1, 100000000
while l < r:
mid = (l+r) // 2
if checkT(mid) < len: # 时间不够,没覆盖完
l = mid + 1
elif checkT(mid) > len: # 时间太大,不是刚好的时候
r = mid
else: # 此时时间 t 刚好完全覆盖区间范围
print(mid)
break
试题 E: 保险箱(15)
代码及思路:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)
rt/252731a671c1fb70aad5355a2c5eeff0.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)