leetcode
leetcode 2151 ~ 2200
查询树中环的长度

查询树中环的长度

难度:

标签:

题目描述

You are given an integer n. There is a complete binary tree with 2n - 1 nodes. The root of that tree is the node with the value 1, and every node with a value val in the range [1, 2n - 1 - 1] has two children where:

  • The left node has the value 2 * val, and
  • The right node has the value 2 * val + 1.

You are also given a 2D integer array queries of length m, where queries[i] = [ai, bi]. For each query, solve the following problem:

  1. Add an edge between the nodes with values ai and bi.
  2. Find the length of the cycle in the graph.
  3. Remove the added edge between nodes with values ai and bi.

Note that:

  • A cycle is a path that starts and ends at the same node, and each edge in the path is visited only once.
  • The length of a cycle is the number of edges visited in the cycle.
  • There could be multiple edges between two nodes in the tree after adding the edge of the query.

Return an array answer of length m where answer[i] is the answer to the ith query.

 

Example 1:

Input: n = 3, queries = [[5,3],[4,7],[2,3]]
Output: [4,5,3]
Explanation: The diagrams above show the tree of 23 - 1 nodes. Nodes colored in red describe the nodes in the cycle after adding the edge.
- After adding the edge between nodes 3 and 5, the graph contains a cycle of nodes [5,2,1,3]. Thus answer to the first query is 4. We delete the added edge and process the next query.
- After adding the edge between nodes 4 and 7, the graph contains a cycle of nodes [4,2,1,3,7]. Thus answer to the second query is 5. We delete the added edge and process the next query.
- After adding the edge between nodes 2 and 3, the graph contains a cycle of nodes [2,1,3]. Thus answer to the third query is 3. We delete the added edge.

Example 2:

Input: n = 2, queries = [[1,2]]
Output: [2]
Explanation: The diagram above shows the tree of 22 - 1 nodes. Nodes colored in red describe the nodes in the cycle after adding the edge.
- After adding the edge between nodes 1 and 2, the graph contains a cycle of nodes [2,1]. Thus answer for the first query is 2. We delete the added edge.

 

Constraints:

  • 2 <= n <= 30
  • m == queries.length
  • 1 <= m <= 105
  • queries[i].length == 2
  • 1 <= ai, bi <= 2n - 1
  • ai != bi

代码结果

运行时间: 91 ms, 内存: 45.9 MB


/*
 * 思路:
 * 使用Java Stream进行同样的操作,简化代码。
 */
import java.util.Arrays;

public class Solution {
    public int[] cycleLengths(int n, int[][] queries) {
        return Arrays.stream(queries)
                     .mapToInt(query -> findCycleLength(query[0], query[1]))
                     .toArray();
    }

    private int findCycleLength(int a, int b) {
        int length = 0;
        while (a != b) {
            if (a > b) {
                a /= 2;
            } else {
                b /= 2;
            }
            length += 1;
        }
        return length + 1; // 加上公共祖先节点的长度
    }
}

解释

方法:

这个题解利用了完全二叉树的性质和位运算来找到从一个节点到另一个节点的路径。对于每个查询,首先确定两个节点中的较大值作为a,较小值作为b。然后计算a和b的二进制表示的长度差,这个长度差l代表了a相对于b在树中的深度差。接下来,通过右移操作将a移动到与b相同的深度,然后通过异或操作计算这两个同深度的节点的差异。这个差异的二进制表示中的1的个数表示了在同一层内,从一个节点到另一个节点需要经过的节点数。环的长度是由从a到b的路径长度加上额外的一条边组成的,因此总长度是a到b同层路径长度的两倍加上从a降到b的层的长度l再加上1(代表新增的边)。

时间复杂度:

O(m)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在处理查询时,需要将a和b中较大的值赋给a,这样的操作有什么特别的意义或优势吗?
确保a是较大的值可以简化路径计算的逻辑。在完全二叉树中,任何节点的父节点的值都比它小。如果a始终是较大的值,可以直接通过右移操作(即除以2的操作)计算a向上移动到与b相同深度的路径。如果b是较大的值,则需要额外的逻辑来处理这种情况,因此统一a为较大值可以使算法更简洁,易于理解。
🦆
在利用位运算计算两节点之间的路径长度时,为什么选择使用异或操作来确定从一个节点到另一个节点需要经过的节点数?
异或操作用于找出两个数在二进制形式下的不同位,每个不同的位代表这两个节点在该位上的路径选择不同(一个选择左子树,一个选择右子树)。因此,异或结果中1的个数表示在同一层内,两个节点在路径上的差异点数量,即需要经过的额外节点数。这种方法直接反映了两个节点在同一层内的距离。
🦆
题解中提到的`深度差l`和右移操作如何确保a和b到达同一深度?这种方法在所有情况下都有效吗?
深度差l是通过计算两个数的二进制表示的长度差得到的。右移操作(即`a >> l`)实质上是将a的二进制表示向右移动l位,这相当于在完全二叉树中将节点a向上移动l层,使其达到与节点b相同的深度。这种方法在完全二叉树的结构下是有效的,因为树的每一层的节点数都是上一层的两倍,节点的值也是按顺序分配的。
🦆
题解提到计算环的长度时,将`同层路径长度的两倍`加上`从a降到b的层的长度l`再加上1,为什么要将同层路径长度乘以两倍?
将同层路径长度乘以两倍是因为环的计算需要考虑从a到b的路径和再从b回到a的路径。即便是在树结构中,环的形成意味着路径必须回到起点,因此计算总路径时,同层内从a到b的距离需要走两次:一次从a到b,一次从b回到a。

相关问题