唉,图真的是我的阿喀琉斯之踵,感觉自己做的其实还可以了,结果一看结果连1500都没有进,差点跑出2000名,即使是国内也在500名之后,连10%都没进,然后看看排名第一的awice大佬,居然只花了7分钟就搞定了全部4道题目,真的是,一言难尽。。。
感觉还是要好好的分析一下了,这次的题目又是两次不该有的错误,一次是自己蠢,一次是算法思路没想清楚,耗掉了大量的时间,也怨不得别人,但是看大佬们几分钟就搞定的感觉自己是不是哪里理解的不对,应该是有什么更好更直观的算法自己没有想到吧。
然后就是第四题,做了50多分钟,给出了一个解法,但是测试的时候发现82个测试样例中通过了73个。。。这简直了,估计就是算法本身可能有问题,针对一些特殊情况没有考虑到。
然后看了一下大佬的解法,发现他们使用了DSU结构(Disjoint Set Union),即并查集结构,这就完全是我的知识盲区了,得花点时间好好补一下课了。
给出题目一的试题链接如下:
这一题本身不难,无非就是对每一个?
进行一下替换就行了,可惜我在比赛的时候想复杂了,想着可不可能会出现什么因为之前的匹配导致后续无法正常插入的情况,事实上,候选集有26个字符,而前后最多只会占用两个字符,所以无论如何都不会出现因为之前的匹配导致后续无法进行替换的情况。
因此,只需要没看到一个?
进行一下替换就行了,亏我竟然因为这个题搞了一次错误提交,简直了,桑心。。。≧ ﹏ ≦
下面,我们给出python的代码实现如下:
class Solution:
def modifyString(self, s: str) -> str:
def get_valid(pre, nxt):
for c in "abcdefghijklmnopqrstuvwxyz":
if c != pre and c != nxt:
return c
ans = []
n = len(s)
for i, c in enumerate(s):
if c != "?":
ans.append(c)
else:
pre = "" if ans == [] else ans[-1]
nxt = '' if i == n-1 else s[i+1]
ans.append(get_valid(pre, nxt))
return "".join(ans)
提交代码评测得到:耗时32ms,占用内存13.9MB。
当前的最优解答耗时28ms,但是看了一下代码思路是完全一致的,因此这里就不再赘述了。
给出题目二的试题链接如下:
这一题思路还是比较清晰的,无非就是对nums1
以及nums2
中的每一个元素到另一个数组中寻找有多少组元素使其乘机恰为其平方。
剩下的就是如何更为高效地统计一个数组中存在多少数据组合使之的乘积恰为一个数。
我们使用首尾逼近法的方式进行计算,得到整体的算法复杂度在 O ( N × M ) O(N \times M) O(N×M)量级,其中, N N N和 M M M分别为两数组的长度。
给出代码实现如下:
class Solution:
def numTriplets(self, nums1: List[int], nums2: List[int]) -> int:
nums1 = sorted(nums1)
nums2 = sorted(nums2)
@lru_cache(None)
def count_valid(tgt, arr):
arr = nums1 if arr == 1 else nums2
n = len(arr)
st = 0
ed = n-1
ans = 0
while st < ed:
if arr[st] * arr[ed] > tgt:
ed -= 1
elif arr[st] * arr[ed] < tgt:
st += 1
else:
if arr[st] == arr[ed]:
ans += (ed-st) * (ed-st+1) // 2
break
i = st
while arr[i] == arr[st]:
i += 1
j = ed
while arr[j] == arr[ed]:
j -= 1
ans += (i-st) * (ed-j)
st = i
ed = j
return ans
ans = 0
for n in nums1:
ans += count_valid(n**2, 2)
for n in nums2:
ans += count_valid(n**2, 1)
return ans
提交代码评测得到:耗时172ms,占用内存14MB。为当前的最优解法。
给出题目三的试题链接如下:
这题就属于典型地在做题的时候想岔了,觉得需要遍历每个元素保留和删除的情况,这直接导致了我的第一次提交失败。
后来仔细一想,因为是确保相邻字符不相同,因此,事实上我们只需要将所有的相同的连续字符删至一个元素即可。如此一来,这道题事实上就十分的简单了。
我们直接给出这一题的python代码实现如下:
class Solution:
def minCost(self, s: str, cost: List[int]) -> int:
ans = sum(cost)
n = len(cost)
last = ''
ans = 0
idx = 0
while idx < n:
ch = s[idx]
co = cost[idx]
if ch != last:
last = ch
idx += 1
else:
st = idx-1
while idx < n and s[idx] == last:
idx += 1
ans += sum(cost[st:idx]) - max(cost[st:idx])
return ans
提交代码评测得到:耗时1184ms,占用内存24.2MB。
当前的最有代码实现耗时908ms,但是看了一下算法思路,和我们是完全一样的,因此,这里就不再赘述了。
给出题目四的试题链接如下:
这道题缝缝补补修修改改搞了一整晚,把之前说的通过的73个测试样例成功提升到了80个,逻辑上的bug应该是都没有了,然后就只剩下超时问题了。。。超时问题。。。问题。。。题。。。
WTF!!!
算了,基本是放弃抢救了,还是和之前一样,这里第一小节先说一下我的思路,然后下一个小节里面扔上我的代码,最后第三个小节再重点看一下大佬们的解法。
因此,如果对前面的糟粕解法没啥兴趣的话,建议直接看第三小节吧。
我自己的解题思路是比较暴力的,显然在保留的优先级上type3的边高于前两者,因此,我们首先考虑type3的边集合,在考虑type1以及type2的边集合。
对于每一个边集合的考察,我们只根据一个思路:
给出python代码实现如下:
from copy import deepcopy
class Solution:
def maxNumEdgesToRemove(self, n: int, edges: List[List[int]]) -> int:
edge_list = [set(), set(), set()]
for edge in edges:
edge_list[edge[0]-1].add((edge[1], edge[2]))
def clean_edge(edge_list, connected_list = []):
edge_set = set()
tmp = {
}
for x, y in edge_list:
tmp[x] = tmp.get(x, []) + [(x, y)]
tmp[y] = tmp.get(y, []) + [(x, y)]
stack = []
new_connected_list = []
for node in tmp.keys():
if any(node in c for c in new_connected_list):
continue
stack = [node]
connected = {
node}
while stack != []:
p = stack.pop(0)
for edge in tmp.get(p, []):
if edge[0] in connected and edge[1] in connected:
continue
if any(edge[0] in c and edge[1] in c for c in connected_list):
for c in connected_list:
if edge[0] in c:
for nd in c:
if nd not in connected:
connected.add(nd)
stack.append(nd)
continue
if edge[0] not in connected:
edge_set.add(edge)
connected.add(edge[0])
stack.append(edge[0])
if edge[1] not in connected:
edge_set.add(edge)
connected.add(edge[1])
stack.append(edge[1])
for c in connected_list:
if edge[0] in c:
for nd in c:
if nd not in connected:
connected.add(nd)
stack.append(nd)
if edge[1] in c:
for nd in c:
if nd not in connected:
connected.add(nd)
stack.append(nd)
new_connected_list.append(connected)
return edge_set, new_connected_list
edge_set, connected_list = clean_edge(edge_list[2], [])
alice_edge, alice_visited= clean_edge(edge_list[1], connected_list)
bob_edge, bob_visited= clean_edge(edge_list[0], connected_list)
if len(alice_visited) == 1 and len(bob_visited) == 1 and len(alice_visited[0]) == n and len(bob_visited[0]) == n:
return len(edges) - len(edge_set) - len(alice_edge) - len(bob_edge)
elif len(connected_list) == 1 and len(connected_list[0]) == n:
return len(edges) - len(edge_set)
else:
return -1
可以看到,代码很长,很丑,且最终提交超时,也许是那里的实现细节上不够完美,也许代码思路上本身就不是最优的思路,但是反正我是实在不想再去折腾这坨代码了。。。>﹏<
看了一下大佬们的解法,发现大佬们普遍使用的是并查集结构(DSU,Disjoint Set Union),可惜这部分就完全处于我的知识盲区了,大约是因为非科班出身的缘故,当年上数据结构的课就完全没有提到过这个结构,看到这个东西真的是惊呆了。。。
因此,这里就暂时先复制awice大佬的解法了,有兴趣的读者可以自行研究一下,我就先不细看了,先去仔细研究一下DSU结构,后面争取搞一篇小文章专门介绍一下这个结构,在那里再尝试搞一下这道题目。
OK,废话不多说了,下面就是awice大佬的解法:
class DSU:
def __init__(self, N):
self.par = list(range(N))
self.sz = [1] * N
def find(self, x):
if self.par[x] != x:
self.par[x] = self.find(self.par[x])
return self.par[x]
def union(self, x, y):
xr, yr = self.find(x), self.find(y)
if xr == yr:
return False
if self.sz[xr] < self.sz[yr]:
xr, yr = yr, xr
self.par[yr] = xr
self.sz[xr] += self.sz[yr]
self.sz[yr] = self.sz[xr]
return True
def size(self, x):
return self.sz[self.find(x)]
class Solution(object):
def maxNumEdgesToRemove(self, N, edges):
for row in edges:
# row[0] -= 1
row[1] -= 1
row[2] -= 1
alice = []
bob = []
both = []
for t, u, v in edges:
if t == 1:
alice.append([u,v])
elif t==2:
bob.append([u,v])
else:
both.append([u,v])
dsu1 = DSU(N)
dsu2 = DSU(N)
ans = 0
for u,v in both:
dsu2.union(u,v)
if not dsu1.union(u, v):
ans += 1
for u,v in alice:
if not dsu1.union(u,v): ans += 1
for u,v in bob:
if not dsu2.union(u,v): ans += 1
if dsu1.size(0) != N:
return -1
if dsu2.size(0) != N:
return -1
return ans
提交后评测得到:耗时2656ms,占用内存54MB。均属于当前第一梯队。