0. 赛后总结
这一次的比赛结果依然是中规中矩,做出来三题,第四题没能搞定,然后排名的话全国202名,全球609名,不算是太挫的成绩,但是也让人开心不起来。
最后一题的话思路上感觉就是一个图的广度优先遍历,但是实际比赛的时候做了一个多小时,感觉就是差一口气,到最后都没有搞定,终究是不爽的紧。
算了,下午休息一下再试试吧,希望这回可以搞定了。
1. 题目一
给出题目一的试题链接如下:
1. 解题思路
这一题只要使用一个栈结构来统计括号的历史最大深度即可。当一个左括号进入时,我们进行一次入栈操作;反之当一个右括号出现时,进行一次出栈操作。
2. 代码实现
给出python代码实现如下:
class Solution:
def maxDepth(self, s: str) -> int:
ans = 0
depth = 0
for c in s:
if c == '(':
depth += 1
ans = max(ans, depth)
elif c == ')':
depth -= 1
return ans
提交代码评测得到:耗时28ms,占用内存14.1MB。属于当前最优代码实现。
2. 题目二
给出题目二的试题链接如下:
1. 解题思路
这一题的思路其实也挺直接的,就是找到每个节点直接相连的边的数目,对其排序之后取最大的两个即可。
但是,需要注意的是,我们需要对结果进行一定的边界修正:
- 如果最大的两个点直接相连,那么秩将是他们的和减一,因此,我们还需要考察以下次大的节点,因为可能存在两个不直接相连的节点因此导致总和大于之前的结果。
2. 代码实现
给出最终的python代码实现如下:
class Solution:
def maximalNetworkRank(self, n: int, roads: List[List[int]]) -> int:
rank = defaultdict(int)
edges = set()
for u, v in roads:
rank[u] += 1
rank[v] += 1
edges.add((u, v))
edges.add((v, u))
rank = sorted(rank.items(), key=lambda x: x[1], reverse=True)
# print(rank)
rank = [x for x in rank if x[1] >= rank[1][1]]
n = len(rank)
# print(edges)
ans = 0
for i in range(n-1):
for j in range(i+1, n):
k = rank[i][1] + rank[j][1] if (rank[i][0], rank[j][0]) not in edges else rank[i][1] + rank[j][1] - 1
ans = max(ans, k)
# print("=" * 10)
return ans
提交代码评测得到:耗时308ms,占用内存15.9MB。为当前最优代码实现。
3. 题目三
给出题目三的试题链接如下:
1. 解题思路
要拼接两个字符串(假设为s1,s2,长度分别为n,m,且有 n ≥ m n\geq m n≥m)令其组成一个回文,那么,必然满足两个条件:
s1[:m] == s2[::-1]
s1[m:]
为一个回文序列
下面,我们对其进行判断实现即可。
2. 代码实现
给出python代码实现如下:
class Solution:
def checkPalindromeFormation(self, a: str, b: str) -> bool:
def is_palindrome(s):
return s == s[::-1]
n = len(a)
i, j = 0, n-1
while a[i] == b[j]:
i += 1
j -= 1
if is_palindrome(a[i:j+1]) or is_palindrome(b[i:j+1]):
return True
i, j = 0, n-1
while b[i] == a[j]:
i += 1
j -= 1
if is_palindrome(a[i:j+1]) or is_palindrome(b[i:j+1]):
return True
return False
提交代码评测得到:耗时72ms,占用内存15.2MB。
当前最优算法的耗时为52ms,看了一下他们的思路,和我们并无根本性的差别,因此这里就不再展开了。
4. 题目四
给出题目四的试题链接如下:
1. 解题思路
如前所述,看到这一题之后最直接的一个思路就是从每一个节点开始按照广度优先搜索找到所有的连通子树,然后统计其中节点的最大距离,然后使用一个计数器进行计数统计。
因此,剩下的就是这个广度优先搜索要怎么实现。
非常遗憾的是,比赛的时候最终还是没能把这个算法实现出来。最后算是提交了一个解法,但是非常遗憾的存在超时问题,31个测试样例中最后一个测试样例始终无法通过。
比赛结束之后参考了一下排名第一的awice大神的解法,发现他不完全是通过bfs来构建树,而是直接考察了所有的节点选用情况,然后统计能够构成一棵树的所有图中的节点间距离。
因此,我们修改我们的思路,考察所有节点的选择情况(由于最多只有15个节点,因此总的可能性最多为 2 15 − 1 2^{15}-1 215−1),然后对其中刚好能够构成一棵树的情况进行考察,统计其最大节点距离。
2. 代码实现
给出最终的python代码实现如下:
class Solution:
def countSubgraphsForEachDiameter(self, n: int, edges: List[List[int]]) -> List[int]:
edge_list = defaultdict(list)
for u, v in edges:
edge_list[u-1].append(v-1)
edge_list[v-1].append(u-1)
# print(edge_list)
distance = [[0 for _ in range(n)] for _ in range(n)]
def bfs(u):
nonlocal distance
stack = [u]
have_visited = {u}
depth = 0
while stack != []:
n = len(stack)
for _ in range(n):
v = stack.pop(0)
distance[u][v] = depth
for node in edge_list[v]:
if node not in have_visited:
have_visited.add(node)
stack.append(node)
depth += 1
# print(distance[u])
return
for i in range(n):
bfs(i)
# print(distance)
def is_connected(nodes):
queue = nodes[:1]
status = [0 if x in nodes else -1 for x in range(n)]
status[nodes[0]] = 1
while queue != []:
m = len(queue)
for _ in range(m):
u = queue.pop(0)
for v in edge_list[u]:
if status[v] == 0:
queue.append(v)
status[v] = 1
return all(x != 0 for x in status)
def cal_max_distance(nodes):
n = len(nodes)
max_dis = 0
for i in range(n-1):
u = nodes[i]
for j in range(i+1, n):
v = nodes[j]
max_dis = max(max_dis, distance[u][v])
return max_dis
ans = [0 for i in range(n)]
for i in range(1, 2**n):
nodes = []
for k in range(n):
if i % 2 == 1:
nodes.append(k)
i = i // 2
if i == 0:
break
if is_connected(nodes):
ans[cal_max_distance(nodes)] += 1
# print("=" * 20)
return ans[1:]
提交代码评测得到:耗时1964ms,占用内存14MB。
而当前的最优解耗时仅28ms,较之我们的解法有近两个量级的性能提升。
因此,我们需要好好考察一下他们的算法实现方法。
3. 算法优化
看了一下当前最优的算法实现,果然它不是像我们这么暴力地遍历求解,他是一个递归算法,对一个原子级别的树,对其增加一个节点,考察最大距离的变化,向上或者向下递归直至构建完成整的一棵树。
因此,其核心在于这个递归公式的写作(代码中的helper函数部分),不过坦率地说这部分的原理并没有完全看懂,只看了个大概,感觉可拓展性也不强,就不想细看了,有兴趣的读者可以自行研究一下,还是蛮有趣的。
这里,就直接只给出大佬们的算法实现了。
class TreeNode:
def __init__(self,val):
self.val=val
self.children={}
class Solution:
def countSubgraphsForEachDiameter(self, n: int, edges: List[List[int]]) -> List[int]:
edgedic=collections.defaultdict(set)
for a,b in edges:
edgedic[a].add(b)
edgedic[b].add(a)
subsize={}
def constructTree(root,nodes,edgedic):
if len(nodes)==1:
return TreeNode(root)
children=collections.defaultdict(set)
def dfs(key,node):
children[key].add(node)
for nnd in edgedic[node]:
if nnd!=root and nnd in nodes and nnd not in children[key]:
dfs(key,nnd)
for nd in edgedic[root]:
if nd in nodes:
dfs(nd,nd)
ans=TreeNode(root)
for k in children:
subsize[k]=len(children[k])
ans.children[k]=constructTree(k,children[k],edgedic)
return ans
root=constructTree(1,set(range(1,n+1)),edgedic)
def helper(root,num):
# root: tree root
# num: number of nodes in the tree
# ret: [{(h,d): #num of subtrees stem from root and has diameter d and height h}, the required n-1 length vector]
if num==2:
return [{(1,1):1},[1]]
ans1=collections.defaultdict(int)
ans2=[0]*(num-1)
tmpans1=[]
for nv in root.children:
tmp1, tmp2=helper(root.children[nv],subsize[nv])
tmp=collections.defaultdict(int)
for i in range(len(tmp2)):
ans2[i]+=tmp2[i]
for h,d in tmp1:
tmp[h+1,max(d,h+1)]+=tmp1[h,d]
tmp[1,1]+=1
tmpans1.append(tmp)
# construct ans1
for tmp in tmpans1:
new=copy.copy(ans1)
for h,d in tmp:
new[h,d]+=tmp[h,d]
for h1,d1 in ans1:
new[max(h1,h),max([h+h1,d,d1])]+=ans1[h1,d1]*tmp[h,d]
ans1=new
for h,d in ans1:
ans2[d-1]+=ans1[h,d]
return [ans1,ans2]
return helper(root,n)[1]