唉,这次又只做出三道题,最后一题一直卡死在了超时问题上,到最后就放弃了,思路上实在不知道还能有什么更好的思路。
最终的排名还是没能挤进国内前一百(108/1660),不过108的数字倒是挺好看的,然后世界270/6572,倒是进了前5%。
最后一题41个测试样例通过37个,卡死在最后4个,回头看看别人的思路吧,不算是什么很好的成绩,不过讲道理,拖着病体能够拿这么个成绩,也该知足了,唉。。。
给出题目一的试题链接如下:
第一题倒是一贯的挺简单的,最简单的思路就是直接做个二层循环就是了,不过我在比赛的时候想岔了,想着那样的算法复杂度恐怕有点高,就想着先将它转换为集合来处理,虽然也没啥问题,但是后来想想,完全没有对时间复杂度有所优化。
给出python代码实现如下:
class Solution:
def countConsistentStrings(self, allowed: str, words: List[str]) -> int:
cnt = 0
allowed = set(allowed)
for w in words:
if all(c in allowed for c in w):
cnt += 1
return cnt
提交代码评测得到:耗时272ms,占用内存16.3MB。
当前的最优算法耗时220ms,但是本质上算法思路是完全相同的。
给出题目二的试题链接如下:
这一题比赛的时候脑子就是晕晕乎乎的,虽然知道直接暴力求解大概率会遇到超时问题,但是还是不信邪的作死试了一下,结果果然呵呵了。
后来仔细分析了一下题目,发现这一题事实上还是挺直观的。
我们考察任意第 i i i个元素的情况,有:
s i = ∑ j ∣ n i − n j ∣ = ∑ j ≤ i ( n i − n j ) + ∑ j > i ( n j − n i ) = ( 2 ⋅ i − n ) × n i − ∑ j ≤ i n j + ∑ j > i n j \begin{aligned} s_i &= \sum_j|n_i - n_j| \\ &= \sum_{j \leq i}(n_i - n_j) + \sum_{j > i}(n_j - n_i) \\ &= (2\cdot i - n) \times n_i - \sum_{j \leq i}n_j + \sum_{j > i}n_j \end{aligned} si=j∑∣ni−nj∣=j≤i∑(ni−nj)+j>i∑(nj−ni)=(2⋅i−n)×ni−j≤i∑nj+j>i∑nj
如此一来,我们可以看到,只要事先求出所有元素的累积求和,就可以将二层循环退化为单层循环关系。
给出最终的python代码实现如下:
class Solution:
def getSumAbsoluteDifferences(self, nums: List[int]) -> List[int]:
n = len(nums)
cumsum = list(accumulate(nums))
res = [0 for i in range(n)]
for i in range(n):
res[i] = (i+1)*nums[i]-cumsum[i] + (cumsum[-1]-cumsum[i]) - (n-i-1)*nums[i]
return res
提交代码评测得到:耗时948ms,占用内存29.5MB。
当前的最优算法实现耗时500ms,但是看了一下他们的算法思路,和我们的算法本质上是完全相同的,因此就不过多展开了,有兴趣的读者可以自行去看一下他们的细节实现。
给出题目三的试题链接如下:
第三题讲真算是一道挺有趣的题目吧,你能够将最优策略想清楚,那么你就能很快地将答案给写出来,否则的话就呵呵了。
我们来考察每一次具体的操作,无论是Alice还是Bob进行一次操作(这里不妨设为Alice,操作为选取第 i i i个石头),带来的影响都是:Alice将会获得相应的分数aliceValues[i]
,而Bob将失去对应的分数bobValues[i]
。因此,该次操作给Alice带来的实际收益应当为aliceValues[i] + bobValues[i]
。
因此,我们对其进行一下排序就能够获得双方的最优选取策略,然后分别计算一下两者的分数就可以知道最终的游戏结果。
给出python代码实现如下:
class Solution:
def stoneGameVI(self, aliceValues: List[int], bobValues: List[int]) -> int:
delta = [(x+y, idx) for idx, (x, y) in enumerate(zip(aliceValues, bobValues))]
delta = sorted(delta, key=lambda x: (-x[0], x[1]))
# print(delta)
alice = 0
bob = 0
for i, (s, idx) in enumerate(delta):
if i % 2 == 0:
alice += aliceValues[idx]
else:
bob += bobValues[idx]
# print(alice, bob)
if alice > bob:
return 1
elif alice == bob:
return 0
else:
return -1
提交代码评测得到:耗时1496ms,占用内存39.4MB。
当前的最优算法实现耗时1128ms,他和我们的思路是相同的,但是在具体的实操过程中,他们并没有像我们这样构造了一个delta进行排序,而是直接对idx进行了排序。
有兴趣的读者可以自行实现一下看看。
给出题目四的试题链接如下:
这一题拿到的第一反应就是动态规划,显然有递推公式:
d p ( i ) = m i n ( d p ( j + 1 ) + f ( i , j ) ) dp(i) = min(dp(j+1) + f(i, j)) dp(i)=min(dp(j+1)+f(i,j))
其中, d p ( i ) dp(i) dp(i)表示运送i~n个箱子所需要的最小运输次数, f ( i , j ) f(i, j) f(i,j)表示一次搬送i~j个箱子所需要的运输路程。
按照题目,i、j满足限制条件:
而另一方面, f ( i , j ) f(i, j) f(i,j)的计算表达式为:
f ( i , j ) = 2 + c o u n t ( i , j ) f(i, j) = 2 + count(i, j) f(i,j)=2+count(i,j)
其中, c o u n t ( i , j ) count(i, j) count(i,j)表示从i到j个箱子之间相邻两个箱子的目的地不同的情况数。
我们可以快速地给出python代码实现如下:
class Solution:
def boxDelivering(self, boxes: List[List[int]], portsCount: int, maxBoxes: int, maxWeight: int) -> int:
n = len(boxes)
dp = [2 for _ in range(n)] + [0]
for i in range(n-2, -1, -1):
dp[i] = dp[i] + dp[i+1]
weight = boxes[i][1]
addition = 2
for j in range(i+1, n):
weight += boxes[j][1]
if weight > maxWeight or j-i+1 > maxBoxes:
break
if boxes[j][0] != boxes[j-1][0]:
addition += 1
dp[i] = min(dp[i], addition + dp[j+1])
return dp[0]
不过,很不幸于到超时问题,41个测试样例只能通过37个,分析其算法复杂度,最优情况下可以为 O ( N ) O(N) O(N),但是在最差的情况下可以退化到 O ( N ⋅ m a x B o x e s ) O(N \cdot maxBoxes) O(N⋅maxBoxes)。
显然,问题就出在了这个地方,可惜我们到最后都没能解决这个问题。
后续看了一下leetcode-cn当中的相关题目解答,发现整体思路事实上确实还是之前的思路,也确实是针对动态规划的滑动窗口部分进行优化,具体的优化思路是通过有序队列的方式进行优化。
我们首先给出之前的递推公式如下:
d p ( i ) = m i n ( d p ( j + 1 ) + f ( i , j ) ) dp(i) = min(dp(j+1) + f(i, j)) dp(i)=min(dp(j+1)+f(i,j))
其中, d p ( i ) dp(i) dp(i)表示从第i个箱子开始到最后所需要的最小运输次数, f ( i , j ) f(i, j) f(i,j)表示一次搬运从第 i i i个箱子到第 j j j个箱子所需要的运输次数。
我们可以将上述递推公式进行一定的变换:
d p ( i ) = m i n ( d p ( j + 1 ) + f ( i , j ) ) = m i n ( d p ( j + 1 ) + 2 + d i f f ( j ) − d i f f ( i ) ) = m i n ( d p ( j + 1 ) + d i f f ( j ) ) + 2 − d i f f ( i ) \begin{aligned} dp(i) &= min(dp(j+1) + f(i, j)) \\ &= min(dp(j+1) + 2 + diff(j) - diff(i)) \\ &= min(dp(j+1) + diff(j)) + 2 - diff(i) \end{aligned} dp(i)=min(dp(j+1)+f(i,j))=min(dp(j+1)+2+diff(j)−diff(i))=min(dp(j+1)+diff(j))+2−diff(i)
其中 d i f f ( i ) diff(i) diff(i)表示从第 0 0 0个箱子到第 i i i个箱子为止,相邻箱子的目标港口不相同的个数。
那么,我们就只要在合法的窗口范围内维护一个有序递减队列就可以了。
当然,如果你不知道有序队列是啥,那么暴力一点通过维护一个有序数组的方式来优化算法,应该也是可以的。
给出相应的python代码实现如下:
class Solution:
def boxDelivering(self, boxes: List[List[int]], portsCount: int, maxBoxes: int, maxWeight: int) -> int:
n = len(boxes)
w = list(accumulate([0] + [x[1] for x in boxes]))
d = [0] + list(accumulate([1 if boxes[i][0] != boxes[i+1][0] else 0 for i in range(n-1)]))
dp = [0 for _ in range(n+1)]
q = []
i = n-1
while i >= 0:
while q != [] and (q[-1][0] - i > maxBoxes or w[q[-1][0]] - w[i] > maxWeight):
q.pop()
while q != [] and q[0][1] >= dp[i+1] + d[i]:
q.pop(0)
q.insert(0, (i+1, dp[i+1] + d[i]))
dp[i] = q[-1][1] + 2 - d[i]
i -= 1
return dp[0]
提交代码评测得到:耗时2376ms,占用内存65.2MB。
当前的最优算法实现耗时1900ms,它的代码有点长,就没有细看,如果有兴趣的读者可以自行去研读一下。
当然,如果可以的话能够在评论区留言说明一下的话就更好了