0. 赛后总结
这一次的比赛真心是五味陈杂,看排名吧,也不算低,国内200,全球500多点,也不算差,即使考虑到晚场的参赛人数少一点,也多少在前10%靠近前5%,不算是个太丢脸的成绩。
但是,无论如何,只做出两题这种情况无论在何时都不是一个让人开心的结果,尤其最后两题遇到的还都是超时问题,后面半个多小时真的是看着题目发呆,横竖不知道该怎么修改,硬生生地最后被用完了时间。
感觉以后做题目的时候还是要好好考虑一下代码的实现效率了,老是这么搞下去迟早有一天把自己搞废了。。。
1. 题目一
给出题目一试题链接如下:
1. 解题思路
这一题最直接的思路就是直接按照题目说的,排序之后去除掉头部的5%数据和尾部的5%的数据,然后求一下平均就是了。
2. 代码实现
给出python代码实现如下:
import numpy
class Solution:
def trimMean(self, arr: List[int]) -> float:
n = len(arr) // 20
arr = sorted(arr)
return numpy.mean(arr[n:-n])
提交代码评测得到:耗时112ms,占用内存28.6MB。
2. 题目二
给出题目二试题链接如下:
1. 解题思路
这一题不知道有没有更好的解题思路,反正我拿到之后的第一反应就是直接暴力检索一下,万幸,没有超时,所以就结束了。
2. 代码实现
给出python代码实现如下:
import math
class Solution:
def bestCoordinate(self, towers: List[List[int]], radius: int) -> List[int]:
strength = defaultdict(float)
for x0, y0, q0 in towers:
for x in range(x0-radius, x0+radius+1):
for y in range(y0-radius, y0+radius+1):
if (x-x0)**2 + (y-y0)**2 > radius**2:
continue
d = math.sqrt((x-x0)**2 + (y-y0)**2)
q = int(q0 / (1+d))
strength[(x, y)] += q
ans = sorted(strength.items(), key=lambda x: (-x[1], x[0]))
return list(ans[0][0])
提交代码评测得到:耗时5544ms,占用内存19.4MB。
当前最优代码耗时2504ms,他的解法比较暴力,但是针对某些情况确实会更有效率。
他的解法是直接遍 ( 0 , 0 ) (0,0) (0,0)到 ( 50 , 50 ) (50,50) (50,50)内的左右坐标点,由于圆心均在这个范围内,因此,这个区域外的点上的信号叠加效果一定低于这个区域内部的点,因此,只需要遍历这部分坐标点即可。
感觉这个解法有点针对题目取巧了,不够通用,不过还是多少有一点参考价值的。
3. 题目三
给出题目三试题链接如下:
1. 解题思路
这一题直觉上一看就是一道动态规划的题目,于是就用了缓存机制直接快速地实现了一个动态规划的算法,然而不幸的是,解法超时了,知道最后都没能搞定。
所以,这里,我们先给出我们的动态规划解法,然后看看大佬们的算法优化方案吧。
我们的动态规划核心思路是说,每一次给出一个终止切割点ed,然后对于这一个切割,显然前面有ed种切割起点的选择方法(0 ~ ed-1),因此,我们可以给出递推公式如下:
d p ( n , k ) = ∑ e d = 1 n − 1 e d × d p ( n − e d , k − 1 ) dp(n, k) = \sum_{ed=1}^{n-1}{ed \times dp(n-ed, k-1)} dp(n,k)=ed=1∑n−1ed×dp(n−ed,k−1)
特别的,当 k = 1 k=1 k=1时,有 d p ( n , 1 ) = n × ( n − 1 ) / 2 dp(n, 1) = n\times (n-1) / 2 dp(n,1)=n×(n−1)/2。
2. 代码实现
给出我们的代码实现如下:
class Solution:
def numberOfSets(self, n: int, k: int) -> int:
MOD = 1000000007
@lru_cache(None)
def dp(n, k):
if k <= 1:
return n * (n-1) // 2
elif k == n-1:
return 1
ans = 0
for ed in range(1, n-k+1):
ans += ed * dp(n-ed, k-1)
return ans % MOD
return dp(n, k)
但是,上述代码会出现超时问题,68个测试样例只能通过60个。
3. 算法优化
考察了大佬们的算法之后,发现我们的算法事实上和正确解法之间只有一步之遥,真的是伤心。
上述算法如果不使用缓存方式来实现,那么可以写作:
class Solution:
def numberOfSets(self, n: int, k: int) -> int:
MOD = 1000000007
dp = [[0 for i in range(n+1)] for _ in range(k+1)]
for i in range(1, n+1):
dp[1][i] = i*(i-1) // 2
for i in range(2, k+1):
dp[i][i+1] = 1
for j in range(i+2, n+1):
for t in range(1, j):
dp[i][j] += t * dp[i-1][j-t]
return dp[k][n]
可以看到,算法复杂度是 O ( k × n 2 ) O(k \times n^2) O(k×n2)。
但是,核心在于递推公式,事实上,我们可以将递推公式改写为:
d p ( n , k ) = ∑ i = 1 n − 1 i × d p ( n − i , k − 1 ) = ∑ i = 1 n − 1 ( ∑ j = 1 i d p ( j , k − 1 ) ) = d p ( n − 1 , k ) + ∑ j = 1 n − 1 d p ( j , k − 1 ) dp(n, k) = \sum_{i=1}^{n-1}{i \times dp(n-i, k-1)} \\ = \sum_{i=1}^{n-1}{(\sum_{j=1}^{i}dp(j, k-1))} \\ = dp(n-1, k) + \sum_{j=1}^{n-1}dp(j, k-1) dp(n,k)=i=1∑n−1i×dp(n−i,k−1)=i=1∑n−1(j=1∑idp(j,k−1))=dp(n−1,k)+j=1∑n−1dp(j,k−1)
那么,我们只需要再用一个数组来储存 ∑ j = 1 n − 1 d p ( j , k − 1 ) \sum_{j=1}^{n-1}dp(j, k-1) ∑j=1n−1dp(j,k−1)的值,就可以减少一次循环次数,将复杂度降至 O ( k × n ) O(k \times n) O(k×n)。
给出最终的代码实现如下:
class Solution:
def numberOfSets(self, n: int, k: int) -> int:
MOD = 1000000007
dp = [[0 for i in range(n+1)] for _ in range(k+1)]
dps = [[0 for i in range(n+1)] for _ in range(k+1)]
for i in range(1, n+1):
dp[1][i] = i*(i-1) // 2
dps[1][i] = dps[1][i-1] + dp[1][i]
for i in range(2, k+1):
dp[i][i+1] = 1
dps[i][i+1] = 1
for j in range(i+2, n+1):
dp[i][j] = (dp[i][j-1] + dps[i-1][j-1]) % MOD
dps[i][j] = (dps[i][j-1] + dp[i][j]) % MOD
return dp[k][n]
提交代码评测得到:耗时1312ms,占用内存63.1MB。
4. 题目四
给出题目四的试题链接如下:
1. 解题思路
这一题我直接的思路是肯定不可能直接暴力地每次去修改已有的数,否则一定会有超时问题,为了规避这个问题,我的思路是存的时候只存原值,但是将操作另行记录,然后返回的时候只要将原数取出,然后加上后续的操作即可。
但是,这样同样会有一个问题,就是当后续的操作非常多的时候,也会存在超时问题。
比赛结束之后看了一下大佬们的解法,其中有一个解法特别的优雅,他的思路是在存取和读出时各自做一次操作。
存取时,令 y = ( x − α ) / β y = (x - \alpha) / \beta y=(x−α)/β,而后存取y;读取时,只要返回 y × β + α y \times \beta + \alpha y×β+α。然后,我们在操作时针对 α \alpha α和 β \beta β进行操作,这样,所有在数据加入之后的后续操作都会在返回时反映出来,而在此之前的操作由于 α \alpha α和 β \beta β的变化都不会被记录在案,因此每个数据只会记录加入之后变化。
但是,如果单纯是除法的话,python后续会遇到精度问题,最终会导致结果错误,因此,该算法中将变化过程修改为了:
y = (x-a) * pow(b, -1, 1e9+7)
其中,pow(x, -1, m)
为python 3.8之后加入的新的功能,其含义是求解方程
x
×
y
m
o
d
(
m
)
≡
1
x \times y \mod(m) \equiv 1
x×ymod(m)≡1中
y
y
y的值。
这样,就修改上述变化公式为:
y
=
(
x
−
α
)
×
β
′
x
r
e
t
=
y
×
β
+
α
β
×
β
′
m
o
d
(
1
0
9
+
7
)
≡
1
y = (x - \alpha) \times \beta' \\ x_{ret} = y \times \beta + \alpha \\ \beta \times \beta' \mod (10^9+7) \equiv 1
y=(x−α)×β′xret=y×β+αβ×β′mod(109+7)≡1
2. 代码实现
给出最终的python代码实现如下:
class Fancy:
def __init__(self):
self.L=[]
self.coef=[1,0]
def append(self, val: int) -> None:
val=(val-self.coef[1])%(10**9+7)
val=(val*pow(self.coef[0], -1, 10**9+7))%(10**9+7)
self.L.append(val)
def addAll(self, inc: int) -> None:
self.coef[1]=(self.coef[1]+inc)%(10**9+7)
def multAll(self, m: int) -> None:
self.coef[0]=(self.coef[0]*m)%(10**9+7)
self.coef[1]=(self.coef[1]*m)%(10**9+7)
def getIndex(self, idx: int) -> int:
if idx not in range(len(self.L)):
return -1
else:
return (self.L[idx]*self.coef[0]+self.coef[1])%(10**9+7)
该解法来自于lilidavid大佬,提交代码评测得到:耗时952ms,占用内存50.3MB,属于当前最优的算法之一。
不得不说,大佬真的是强,这个解法真心佩服得五体投地。