Find Kth Largest and Tree Ancestors
Company: Meta
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates algorithmic problem-solving and data-structure competencies, focusing on selection algorithms and time/space complexity reasoning for the k-th largest element and tree traversal, lowest common ancestor discovery with attention to BST properties, parent pointers, and external-memory constraints.
Part 1: K-th Largest Element in an Unsorted Array
Constraints
- 1 <= len(nums) <= 100000
- -1000000000 <= nums[i] <= 1000000000
- 1 <= k <= len(nums)
- Duplicate values are allowed and count independently.
Examples
Input: ([3, 2, 1, 5, 6, 4], 2)
Expected Output: 5
Explanation: Sorted descending is [6, 5, 4, 3, 2, 1], so the 2nd largest is 5.
Input: ([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)
Expected Output: 4
Explanation: Sorted descending is [6, 5, 5, 4, 3, 3, 2, 2, 1].
Input: ([-1, -1, -2, -3], 2)
Expected Output: -1
Explanation: Duplicates count separately; the two largest elements are -1 and -1.
Input: ([10], 1)
Expected Output: 10
Explanation: Single-element edge case.
Input: ([7, 7, 7], 3)
Expected Output: 7
Explanation: All elements are equal, including the smallest/largest boundary ranks.
Hints
- The k-th largest element is at index len(nums) - k if the array were sorted in ascending order.
- Instead of sorting both sides recursively, partition around a pivot and continue only into the side containing the target index.
Part 2: Lowest Common Ancestor in a Binary Tree
Constraints
- 2 <= len(values) <= 10000
- len(left) == len(right) == len(values)
- All values are unique integers.
- left[i] and right[i] are either -1 or valid node indices.
- The arrays describe a valid binary tree rooted at index 0.
- p and q are distinct values that both appear in values.
Examples
Input: ([3, 5, 1, 6, 2, 0, 8, 7, 4], [1, 3, 5, -1, 7, -1, -1, -1, -1], [2, 4, 6, -1, 8, -1, -1, -1, -1], 5, 1)
Expected Output: 3
Explanation: Nodes 5 and 1 are in different subtrees of root 3.
Input: ([3, 5, 1, 6, 2, 0, 8, 7, 4], [1, 3, 5, -1, 7, -1, -1, -1, -1], [2, 4, 6, -1, 8, -1, -1, -1, -1], 5, 4)
Expected Output: 5
Explanation: Node 5 is an ancestor of node 4, so 5 is the LCA.
Input: ([3, 5, 1, 6, 2, 0, 8, 7, 4], [1, 3, 5, -1, 7, -1, -1, -1, -1], [2, 4, 6, -1, 8, -1, -1, -1, -1], 7, 4)
Expected Output: 2
Explanation: Nodes 7 and 4 are the two children of node 2.
Input: ([1, 2], [1, -1], [-1, -1], 1, 2)
Expected Output: 1
Explanation: Smallest valid tree edge case: the root is one of the queried nodes.
Input: ([1, 2, 3, 4], [-1, -1, -1, -1], [1, 2, 3, -1], 3, 4)
Expected Output: 3
Explanation: Skewed-tree edge case where one queried node is an ancestor of the other.
Hints
- A DFS call can return the index of a target or LCA found in that subtree, or -1 if neither target is present.
- If both left and right recursive calls return non-empty results, the current node is where the two target paths meet.
Part 3: Lowest Common Ancestor Optimized for a Binary Search Tree
Constraints
- 2 <= len(values) <= 100000
- len(left) == len(right) == len(values)
- All values are unique integers.
- The arrays describe a valid binary search tree rooted at index 0.
- For every node, all values in its left subtree are smaller and all values in its right subtree are larger.
- p and q are distinct values that both appear in values.
Examples
Input: ([6, 2, 8, 0, 4, 7, 9, 3, 5], [1, 3, 5, -1, 7, -1, -1, -1, -1], [2, 4, 6, -1, 8, -1, -1, -1, -1], 2, 8)
Expected Output: 6
Explanation: Values 2 and 8 split at the root 6.
Input: ([6, 2, 8, 0, 4, 7, 9, 3, 5], [1, 3, 5, -1, 7, -1, -1, -1, -1], [2, 4, 6, -1, 8, -1, -1, -1, -1], 2, 4)
Expected Output: 2
Explanation: Node 2 is an ancestor of node 4.
Input: ([6, 2, 8, 0, 4, 7, 9, 3, 5], [1, 3, 5, -1, 7, -1, -1, -1, -1], [2, 4, 6, -1, 8, -1, -1, -1, -1], 3, 5)
Expected Output: 4
Explanation: Values 3 and 5 are on opposite sides of node 4.
Input: ([2, 1], [1, -1], [-1, -1], 2, 1)
Expected Output: 2
Explanation: Two-node edge case where the root is one of the queried nodes.
Input: ([1, 2, 3], [-1, -1, -1], [1, 2, -1], 2, 3)
Expected Output: 2
Explanation: Right-skewed BST edge case.
Hints
- Compare both p and q to the current node's value at the same time.
- The first node whose value lies between p and q, inclusive, is the LCA.
Part 4: Lowest Common Ancestor with Parent Pointers
Constraints
- 2 <= len(parent) <= 100000
- parent contains exactly one -1 entry, representing the root.
- Every other parent[i] is a valid node index.
- The parent array describes a valid rooted tree.
- 0 <= p, q < len(parent), and p != q.
Examples
Input: ([-1, 0, 0, 1, 1, 2, 2], 3, 4)
Expected Output: 1
Explanation: Nodes 3 and 4 are siblings under node 1.
Input: ([-1, 0, 0, 1, 1, 2, 2], 3, 5)
Expected Output: 0
Explanation: The paths meet first at the root 0.
Input: ([-1, 0, 0, 1, 1, 2, 2], 1, 4)
Expected Output: 1
Explanation: Node 1 is an ancestor of node 4.
Input: ([-1, 0], 0, 1)
Expected Output: 0
Explanation: Smallest valid tree edge case.
Input: ([-1, 0, 1, 2, 3], 4, 2)
Expected Output: 2
Explanation: Skewed chain edge case where one node is an ancestor of the other.
Hints
- Following parent pointers from a node gives its path to the root.
- If the two nodes are at the same depth, moving them upward one step at a time preserves the LCA.
Part 5: Lowest Common Ancestor When the Tree Is Too Large to Fit in Memory
Constraints
- 2 <= len(parent) == len(depth) <= 100000
- parent contains exactly one -1 entry, representing the root.
- depth[root] == 0, and depth[i] == depth[parent[i]] + 1 for non-root nodes.
- The parent array describes a valid rooted tree.
- 0 <= p, q < len(parent), and p != q.
- The algorithm should not build child lists, traverse unrelated subtrees, or store complete root-to-node paths.
Examples
Input: ([-1, 0, 0, 1, 1, 2, 2], [0, 1, 1, 2, 2, 2, 2], 3, 5)
Expected Output: 0
Explanation: Nodes 3 and 5 are in different root subtrees, so the LCA is 0.
Input: ([-1, 0, 0, 1, 1, 2, 2], [0, 1, 1, 2, 2, 2, 2], 3, 4)
Expected Output: 1
Explanation: Both nodes have the same parent 1.
Input: ([-1, 0, 0, 1, 1, 2, 2], [0, 1, 1, 2, 2, 2, 2], 1, 4)
Expected Output: 1
Explanation: Node 1 is an ancestor of node 4; the deeper node is lifted first.
Input: ([-1, 0], [0, 1], 0, 1)
Expected Output: 0
Explanation: Smallest valid tree edge case.
Input: ([-1, 0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 5], 5, 3)
Expected Output: 3
Explanation: Deep-chain edge case; depth alignment immediately lifts node 5 to node 3.
Hints
- Depth metadata tells you which node is deeper without walking all the way to the root first.
- After both nodes are at equal depth, move them upward together; the first equal id is the LCA.