blog-cover-image

Top Machine Learning Engineer Interview Questions at Meta

In this article, we solve and explain four commonly asked technical questions, diving into the concepts and approaches required to excel at such interviews. Each problem is dissected with clear explanations, step-by-step solutions, code implementations, and mathematical reasoning to help you master these essential topics.

Machine Learning Engineer Question from Meta & Amazon: Detailed Solutions and Explanations


1. Shortest Knight Distance on an Infinite Chessboard (Meta)

Problem Statement

Given an infinite chessboard, find the shortest number of moves required for a knight to travel from position A to position B. Both positions are specified as coordinates, for example, A = (x1, y1) and B = (x2, y2).

Understanding the Problem

A knight moves in an "L" shape: two squares in one direction and one square perpendicular to that. The main challenge is to efficiently compute the minimum number of moves (the shortest path) from A to B, even when the board has no boundaries.

Key Concepts

  • Breadth-First Search (BFS): Ideal for unweighted shortest path problems.
  • Symmetry: The infinite chessboard allows us to simplify the problem by focusing on the relative position.
  • State Representation: Each state is a coordinate (x, y).

Mathematical Formulation

Let the knight start at position \( A = (x_1, y_1) \) and end at position \( B = (x_2, y_2) \). Define the relative coordinates:

\[ (dx, dy) = (|x_2 - x_1|, |y_2 - y_1|) \]

Due to symmetry, we can always map the problem to moving from (0, 0) to (dx, dy).

BFS Approach

We use BFS to explore all possible moves until we reach the target position. BFS guarantees the shortest path in an unweighted grid.

BFS guarantees the shortest path because it explores all possible positions at a given number of moves before moving on to positions that require more moves. 

For example, suppose you want to move a knight from (0, 0) to (2, 1). BFS will first consider all positions reachable in one move, which include (2, 1) and (1, 2). Since (2, 1) is reached in the first layer, BFS immediately returns 1 as the minimum number of moves. If the target were farther, say (3, 3), BFS would explore all positions at distance 1, then all at distance 2, and only then reach (3, 3) at distance 2, ensuring that no shorter path is missed. This "wavefront" property is what makes BFS optimal for shortest path problems on unweighted grids such as the knight's moves.


from collections import deque

def min_knight_moves(x1, y1, x2, y2):
    # Relative position
    dx, dy = abs(x2 - x1), abs(y2 - y1)
    # Due to symmetry, always ensure dx >= dy
    dx, dy = max(dx, dy), min(dx, dy)
    
    # Set of possible knight moves
    moves = [
        (2, 1), (1, 2), (-2, 1), (-1, 2),
        (2, -1), (1, -2), (-2, -1), (-1, -2)
    ]
    
    visited = set()
    queue = deque()
    queue.append((0, 0, 0))  # (x, y, distance)
    visited.add((0, 0))
    
    while queue:
        x, y, dist = queue.popleft()
        if (x, y) == (dx, dy):
            return dist
        for mx, my in moves:
            nx, ny = abs(x + mx), abs(y + my)
            # Always keep nx >= ny for symmetry
            nx, ny = max(nx, ny), min(nx, ny)
            if (nx, ny) not in visited and nx >= 0 and ny >= 0:
                visited.add((nx, ny))
                queue.append((nx, ny, dist + 1))
    return -1  # Should never reach here

Time and Space Complexity

  • Time Complexity: \( O(d^2) \), where \( d = dx + dy \), since BFS may visit all positions within a certain Manhattan distance.
  • Space Complexity: \( O(d^2) \).

Example


print(min_knight_moves(0, 0, 5, 5))  # Output: 4

Key Takeaways

  • BFS is the optimal approach for shortest path on a grid with uniform moves.
  • Symmetry dramatically reduces the search space.

2. Intersection of Two Integer Arrays (Amazon)

Problem Statement

Write a function that takes two integer arrays and returns their intersection. The result should contain only unique elements.

Understanding the Problem

Given two arrays, we want to find the set of integers that appear in both arrays, with no duplicates in the result.

Key Concepts

  • Hash Set: Efficient for checking membership in constant time.
  • Set Operations: Python's set intersection operator or method is very efficient.

Algorithm

  1. Convert both arrays to sets to remove duplicates.
  2. Compute the intersection of the two sets.
  3. Return the result as a list.

def array_intersection(nums1, nums2):
    set1 = set(nums1)
    set2 = set(nums2)
    return list(set1 & set2)

Time and Space Complexity

  • Time Complexity: \( O(n + m) \), where \( n \) and \( m \) are the lengths of the two arrays.
  • Space Complexity: \( O(n + m) \).

Alternative Approach: Without Built-in Set Operations

If built-in set operations are not allowed, you can use a dictionary or manually check membership.


def array_intersection_no_set(nums1, nums2):
    seen = {}
    for num in nums1:
        seen[num] = True
    result = []
    for num in nums2:
        if seen.get(num, False) and num not in result:
            result.append(num)
    return result

Example


print(array_intersection([1, 2, 2, 3], [2, 2, 4]))  # Output: [2]

3. Find the Heaviest Marble in 3 Weighings (Amazon)

Problem Statement

You have 9 marbles. One is heavier, the rest are identical in weight. Use a balance scale to find the heaviest marble with only 3 weighings.

Understanding the Problem

This is a classic divide and conquer problem often asked to test logical reasoning and algorithmic thinking.

Key Concepts

  • Divide and Conquer: Recursively partitioning the problem.
  • Balance Scale: Can only compare weights, not measure them.

Mathematical Reasoning

At each weighing, you can partition the set into three groups. At each step, you reduce the problem to a third of its size:

\[ 9 \xrightarrow{\text{1st weigh}} 3 \xrightarrow{\text{2nd weigh}} 1 \]

Since \( 3^2 = 9 \), two weighings get us to three marbles, and a final weighing identifies the heaviest.

Step-by-Step Solution

  1. First Weighing: Divide the 9 marbles into 3 groups of 3: G1, G2, G3.
    Weigh G1 vs G2.
    • If G1 = G2: The heavier marble is in G3.
    • If G1 > G2: The heavier marble is in G1.
    • If G1 < G2: The heavier marble is in G2.
  2. Second Weighing: Take the identified group of 3 marbles. Divide into three singles: M1, M2, M3.
    Weigh M1 vs M2.
    • If M1 = M2: The heavier marble is M3.
    • If M1 > M2: The heavier marble is M1.
    • If M1 < M2: The heavier marble is M2.

Algorithmic Representation


def find_heaviest_marble(marbles, weigh):
    """
    marbles: List of marbles (can be indices or objects)
    weigh: Function to compare two groups, returns -1, 0, 1
    """
    group1, group2, group3 = marbles[:3], marbles[3:6], marbles[6:]
    result = weigh(group1, group2)
    if result == 0:
        heavy_group = group3
    elif result > 0:
        heavy_group = group1
    else:
        heavy_group = group2
    # Now, heavy_group has 3 marbles
    m1, m2, m3 = heavy_group
    result = weigh([m1], [m2])
    if result == 0:
        return m3
    elif result > 0:
        return m1
    else:
        return m2

Example Table

Step Groups/Marbles Weighing Outcome
1 G1 (1,2,3) vs G2 (4,5,6) First weighing Heaviest is in heavier group or G3 if equal
2 M1, M2, M3 Second weighing Compare M1 vs M2, choose heavier or M3 if equal

Key Takeaways

  • Divide and conquer efficiently narrows the search space.
  • Three weighings suffice for up to 9 marbles.

4. Next Greatest Element in an Array (Meta)

Problem Statement

For each element in an array, find the next greater element to its right. If no such element exists, output -1.

Understanding the Problem

Given an array \( A = [a_0, a_1, \ldots, a_{n-1}] \), for each \( a_i \), find the smallest \( j > i \) such that \( a_j > a_i \). If none exists, output -1 for that position.

Key Concepts

  • Stack Data Structure: Used for efficient right-to-left processing.
  • Monotonic Stack: Stack stores indices of elements for which we haven't found the next greater element yet.

Mathematical Explanation

At each index \( i \), we want to find:

\[ \text{NGE}[i] = a_j, \quad \text{where} \quad j > i \text{ and } a_j > a_i \text{ and } j \text{ is minimal} \]

Efficient Stack-Based Algorithm

  1. Initialize an empty stack and a result array of size n, filled with -1.
  2. Traverse the array from right to left.
  3. While the stack is not empty and the top of the stack is less than or equal to the current element, pop the stack.
  4. If the stack is not empty after popping, set result[i] = stack[-1].
  5. Push the current element onto the stack.

def next_greater_element(arr):
    n = len(arr)
    result = [-1] * n
    stack = []
    for i in range(n - 1, -1, -1):
        while stack and stack[-1] <= arr[i]:
            stack.pop()
        if stack:
            result[i] = stack[-1]
        stack.append(arr[i])
    return result

Time and Space Complexity

  • Time Complexity: \( O(n) \), each element is pushed and popped at most once.
  • Space Complexity: \( O(n) \) for the stack and result array.

Example


print(next_greater_element([4, 5, 2, 10]))
# Output: [5, 10, 10, -1]
Index Value Next Greater Element
0 4 5
1 5 10
2 2 10
3 10 -1

Further Exploration: Extending the Problems

1. Infinite Chessboard Variants

  • How would the solution change if the knight was replaced by another piece, such as a bishop or rook?
  • Can you extend the solution to 3D (or higher-dimensional) grids, relevant for certain robotics or simulation applications?

2. Array Intersection Variations

  • How would you handle arrays with billions of elements distributed across multiple servers? (Hint: Use distributed hash sets or Bloom filters.)
  • What if you needed the intersection with counts, i.e., the number of times each element appears in both arrays?

3. Balance Scale Generalization

  • How many weighings are needed for n marbles? The general formula is \( \lceil \log_3 n \rceil \).
  • How would you adapt the approach if there could be a lighter marble instead of a heavier one?

4. Next Greater Element Extensions

  • How would you modify the algorithm to find the next smaller element to the right, or the previous greater element to the left?
  • Can you apply this logic to circular arrays, common in scheduling or buffer problems?

Frequently Asked Questions

Question Quick Answer
Why is BFS better than DFS for the knight shortest path? BFS always finds the shortest path in an unweighted graph, while DFS may not.
Can hash sets handle very large arrays? Yes, but memory may become a bottleneck; consider Bloom filters or distributed approaches for huge datasets.
Is divide and conquer always optimal for limited testing scenarios? For balance scale problems, yes. For other constraints, consider all options and prove optimality.
When should I use a monotonic stack? Whenever you need to track the next/previous greater/smaller elements efficiently.

 

Related Articles