Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

947. Most Stones Removed with Same Row or Column - Leetcode Solution

Code Implementation

class Solution:
    def removeStones(self, stones):
        parent = {}

        def find(x):
            if parent.setdefault(x, x) != x:
                parent[x] = find(parent[x])
            return parent[x]

        for x, y in stones:
            # Use ~y to avoid collision between x and y
            parent.setdefault(x, x)
            parent.setdefault(~y, ~y)
            parent[find(x)] = find(~y)

        roots = set(find(x) for x, y in stones)
        return len(stones) - len(roots)
      
class Solution {
public:
    int removeStones(vector<vector<int>>& stones) {
        unordered_map<int, int> parent;

        function<int(int)> find = [&](int x) {
            if (!parent.count(x)) parent[x] = x;
            if (parent[x] != x) parent[x] = find(parent[x]);
            return parent[x];
        };

        for (auto& stone : stones) {
            int x = stone[0], y = ~stone[1];
            parent[x] = x;
            parent[y] = y;
            parent[find(x)] = find(y);
        }

        unordered_set<int> roots;
        for (auto& stone : stones) {
            roots.insert(find(stone[0]));
        }
        return stones.size() - roots.size();
    }
};
      
class Solution {
    Map<Integer, Integer> parent = new HashMap<>();

    private int find(int x) {
        if (!parent.containsKey(x)) parent.put(x, x);
        if (parent.get(x) != x) parent.put(x, find(parent.get(x)));
        return parent.get(x);
    }

    public int removeStones(int[][] stones) {
        for (int[] stone : stones) {
            int x = stone[0], y = ~stone[1];
            parent.putIfAbsent(x, x);
            parent.putIfAbsent(y, y);
            parent.put(find(x), find(y));
        }
        Set<Integer> roots = new HashSet<>();
        for (int[] stone : stones) {
            roots.add(find(stone[0]));
        }
        return stones.length - roots.size();
    }
}
      
var removeStones = function(stones) {
    const parent = {};

    function find(x) {
        if (!(x in parent)) parent[x] = x;
        if (parent[x] !== x) parent[x] = find(parent[x]);
        return parent[x];
    }

    for (const [x, y] of stones) {
        parent[x] = x;
        parent[~y] = ~y;
        parent[find(x)] = find(~y);
    }

    const roots = new Set();
    for (const [x, y] of stones) {
        roots.add(find(x));
    }
    return stones.length - roots.size;
};
      

Problem Description

You are given a 2D grid with a number of stones, each represented by their coordinates [x, y] in the grid. A move consists of removing a stone that shares either its row or column with another stone (i.e., there is at least one other stone in the same row or column).

The task is to determine the maximum number of stones you can remove from the grid. You may not remove a stone if it is the only stone in its row and column. Each move must remove a single stone, and you cannot reuse or re-add any stones once removed.

Constraints:

  • Each stone is uniquely placed at [x, y].
  • 1 ≤ number of stones ≤ 1000
  • 0 ≤ x, y ≤ 104
  • There is always at least one valid solution.

Thought Process

At first glance, one might try to simulate the moves: for each stone, check if it shares a row or column with another, and remove it if possible. But this approach is inefficient and complicated, especially as stones are removed and the grid changes.

Instead, let's look for a pattern: if stones are connected by rows or columns, they form connected groups or "islands". Within each group, we can always remove all stones except one (since each removal leaves at least one connection until only one stone remains). Thus, the problem reduces to counting these connected groups.

The key insight is: maximum stones removed = total stones - number of connected components. The challenge then becomes efficiently finding these groups.

To do this, we can use a Union-Find (Disjoint Set Union, DSU) data structure to group stones that are connected via rows or columns. This avoids brute-force and leverages efficient data structures for grouping.

Solution Approach

  • Model stones as nodes in a graph: Each stone is a node. If two stones share a row or column, they are connected.
  • Use Union-Find to group connections: For each stone, union its row and column (using unique identifiers to avoid collision). This way, all stones in the same row or column become part of the same group.
  • Count the number of groups: After processing all stones, count how many unique root parents there are in the Union-Find structure. Each unique parent represents a connected component.
  • Calculate result: The answer is number of stones - number of groups, since in each group we can remove all stones except one.

Why use ~y (bitwise NOT) for columns? Since both x and y can be in the same range, we use ~y to create a unique identifier for columns, ensuring rows and columns do not overlap in the Union-Find structure.

  1. Initialize a Union-Find structure.
  2. For each stone [x, y]:
    • Union x (row) and ~y (column).
  3. After all unions, count unique roots among all x (rows) and ~y (columns) used.
  4. Return number of stones - number of unique roots.

Example Walkthrough

Example Input: [[0,0],[0,1],[1,0],[1,2],[2,2],[2,3]]

  • Stones: (0,0), (0,1), (1,0), (1,2), (2,2), (2,3)
  • Connect stones sharing rows or columns:
    • (0,0) connects to (0,1) (same row)
    • (0,0) connects to (1,0) (same column)
    • (1,2) connects to (2,2) (same column)
    • (2,2) connects to (2,3) (same row)
  • Union-Find links all these stones into a single group, except (2,3) which connects via (2,2).
  • After unioning, all stones are in one connected component.
  • Number of groups: 1
  • Number of stones: 6
  • Maximum stones removed: 6 - 1 = 5

Step-by-step, each stone is unioned with its row and column, grouping them. At the end, all stones are part of one group, so we can remove all but one.

Time and Space Complexity

  • Brute-force approach: For each stone, scan all others to check for row/column matches and simulate removals. This is O(N^2) per move, and potentially exponential overall as the configuration changes.
  • Optimized Union-Find approach:
    • Each union and find operation is nearly O(1) due to path compression and union by rank (amortized).
    • We perform up to 2N operations (one for each row and column per stone).
    • Overall time complexity: O(N * α(N)), where α is the inverse Ackermann function (very slow-growing, so nearly linear).
    • Space complexity: O(N) for parent mappings.

Thus, the optimized solution is efficient and suitable for large inputs.

Summary

The "Most Stones Removed with Same Row or Column" problem is elegantly solved by recognizing that the problem reduces to finding connected groups of stones. Using Union-Find, we efficiently group stones sharing rows or columns, and count the number of groups. The maximum stones we can remove is simply the total stones minus the number of groups. This approach is both efficient and conceptually clean, leveraging classic data structures to transform a seemingly complex simulation into a simple counting problem.