返回

LeetCode 周赛 341:模拟、树上差分、Tarjan 离线 LCA、DFS

闲谈

LeetCode 周赛 341 题解

挑战与收获

上周末,我参加了 LeetCode 周赛 341。比赛题目涵盖了模拟、树上差分、离线 LCA 和 DFS 等算法。尽管前三道题相对简单,但第四道题却给我带来了不小的挑战。本文将详细解析比赛题目,分享我的解题思路和收获。

第 1 题:模拟

本题要求计算数组中满足特定条件的元组数量。可以使用三重循环遍历数组,检查每个元组是否满足条件,然后再计数。

第 2 题:模拟

本题要求计算数组中逆序对的数量。同样可以使用二重循环遍历数组,计算每个元素右侧比其小的元素数量,然后累加得到总的逆序对数量。

第 3 题:树上差分

本题要求计算给定树中每个结点为根的子树中指定结点数量。可以使用 DFS 算法遍历树,并利用树上差分更新子树中指定结点的数量。

第 4 题:Tarjan 离线 LCA、DFS

本题要求计算给定树中任意两点之间的最大遗传差异。遗传差异定义为两点之间路径上最大高度减去最小高度。可以使用 Tarjan 离线 LCA 算法预处理树,然后使用 DFS 算法计算每对查询结点之间的最大遗传差异。

代码示例

# 第 1 题:模拟
def countGoodTriplets(arr1, arr2):
  cnt = 0
  for i in range(len(arr1)):
    for j in range(i + 1, len(arr1)):
      for k in range(j + 1, len(arr1)):
        if abs(arr1[i] - arr1[j]) <= 1 and abs(arr1[j] - arr1[k]) <= 1 and abs(arr1[i] - arr1[k]) <= 1 and abs(arr2[i] - arr2[j]) <= 1 and abs(arr2[j] - arr2[k]) <= 1 and abs(arr2[i] - arr2[k]) <= 1:
          cnt += 1
  return cnt

# 第 2 题:模拟
def countBadPairs(nums):
  cnt = 0
  for i in range(len(nums)):
    for j in range(i + 1, len(nums)):
      if nums[i] > nums[j] and i < j:
        cnt += 1
  return cnt

# 第 3 题:树上差分
def countSubTrees(n, edges, queries):
  tree = [[] for _ in range(n + 1)]
  for u, v in edges:
    tree[u].append(v)
    tree[v].append(u)

  def dfs(u, p):
    sub[u] = 1
    for v in tree[u]:
      if v != p:
        dfs(v, u)
        sub[u] += sub[v]

  sub = [0] * (n + 1)
  dfs(1, 0)

  res = []
  for q in queries:
    res.append(sub[q])
  return res

# 第 4 题:Tarjan 离线 LCA、DFS
def maxGeneticDifference(parents, queries):
  n = len(parents)
  tree = [[] for _ in range(n)]
  for i in range(1, n):
    tree[parents[i]].append(i)

  q = [[0, n - 1]]
  for l, r in queries:
    q.append([l, r])

  LCA = [-1] * n
  visited = [False] * n
  ancestor = [[-1] * 20 for _ in range(n)]

  def dfs(u, p):
    visited[u] = True
    ancestor[u][0] = p
    for i in range(1, 20):
      ancestor[u][i] = ancestor[ancestor[u][i - 1]][i - 1]

    for v in tree[u]:
      if not visited[v]:
        dfs(v, u)

  def lca(u, v):
    if LCA[u] != -1:
      return LCA[u]
    if LCA[v] != -1:
      return LCA[v]

    if u == v:
      return u

    if ancestor[u][0] == v:
      return v

    if ancestor[v][0] == u:
      return u

    for i in range(19, -1, -1):
      if ancestor[u][i] != -1 and ancestor[u][i] != ancestor[v][i]:
        u = ancestor[u][i]
        v = ancestor[v][i]

    return ancestor[u][0]

  dfs(0, 0)

  for i in range(1, 20):
    for u in range(n):
      ancestor[u][i] = ancestor[ancestor[u][i - 1]][i - 1]

  for i in range(1, len(q)):
    l, r = q[i]
    LCA[i] = lca(l, r)

  res = []
  for i in range(1, len(q)):
    l, r = q[i]
    dist = 0
    u = l
    while u != LCA[i]:
      dist += 1
      u = ancestor[u][0]

    v = r
    while v != LCA[i]:
      dist += 1
      v = ancestor[v][0]

    res.append(dist)

  return res

总结

本次周赛的题目涵盖了多种算法技巧。前三道题相对简单,考验的是基本算法的实现能力。第四道题则比较复杂,需要综合运用离线 LCA 和 DFS 算法。通过本次比赛,我不仅巩固了已有算法知识,还学习了新的算法技巧,对我的算法能力提升颇有帮助。

常见问题解答

1. Tarjan 离线 LCA 算法是什么?

Tarjan 离线 LCA 算法是一种用于离线计算树中任意两点之间的最近公共祖先(LCA)的算法。它利用了树的性质,通过预处理和后序遍历,可以高效地计算所有查询结点之间的 LCA。

2. DFS 算法如何用于解决本题?

DFS 算法用于遍历树,并在遍历过程中计算每个结点到根结点的距离。利用这些距离,可以计算任意两点之间的最大遗传差异。

3. 本题中遗传差异的计算方法是什么?

遗传差异定义为两点之间路径上最大高度减去最小高度。在计算过程中,需要考虑两点到 LCA 的距离,以及 LCA 到两点的距离。

4. 本题中为什么需要使用树上差分?

树上差分是一种动态维护树中指定结点数量的技巧。在本题中,我们需要计算每个结点为根的子树中指定结点的数量,可以使用树上差分高效地实现。

5. 本次比赛最具挑战性的题目是什么?

本次比赛最具挑战性的题目是第 4 题,它需要综合运用 Tarjan 离线 LCA 和 DFS 算法,对算法能力和逻辑思维提出了较高的要求。