LeetCode笔记:Biweekly Contest 37 比赛记录

  • LeetCode笔记:Biweekly Contest 37
    • 0. 赛后总结
    • 1. 题目一
      • 1. 解题思路
      • 2. 代码实现
    • 2. 题目二
      • 1. 解题思路
      • 2. 代码实现
    • 3. 题目三
      • 1. 解题思路
      • 2. 代码实现
      • 3. 算法优化
    • 4. 题目四
      • 1. 解题思路
      • 2. 代码实现

0. 赛后总结

这一次的比赛真心是五味陈杂,看排名吧,也不算低,国内200,全球500多点,也不算差,即使考虑到晚场的参赛人数少一点,也多少在前10%靠近前5%,不算是个太丢脸的成绩。

但是,无论如何,只做出两题这种情况无论在何时都不是一个让人开心的结果,尤其最后两题遇到的还都是超时问题,后面半个多小时真的是看着题目发呆,横竖不知道该怎么修改,硬生生地最后被用完了时间。

感觉以后做题目的时候还是要好好考虑一下代码的实现效率了,老是这么搞下去迟早有一天把自己搞废了。。。

1. 题目一

给出题目一试题链接如下:

  • 5122. 删除某些元素后的数组均值

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. 题目二

给出题目二试题链接如下:

  • 5528. 网络信号最好的坐标

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. 题目三

给出题目三试题链接如下:

  • 5527. 大小为 K 的不重叠线段的数目

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=1n1ed×dp(ned,k1)

特别的,当 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×(n1)/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=1n1i×dp(ni,k1)=i=1n1(j=1idp(j,k1))=dp(n1,k)+j=1n1dp(j,k1)

那么,我们只需要再用一个数组来储存 ∑ j = 1 n − 1 d p ( j , k − 1 ) \sum_{j=1}^{n-1}dp(j, k-1) j=1n1dp(j,k1)的值,就可以减少一次循环次数,将复杂度降至 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. 题目四

给出题目四的试题链接如下:

  • 5530. 奇妙序列

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,属于当前最优的算法之一。

不得不说,大佬真的是强,这个解法真心佩服得五体投地。

你可能感兴趣的:(leetcode笔记,leetcode,算法,python)