Most Stones Removed with Same Row or Column
Max stones removable while keeping each connected component non-empty — DSU on row/column unions.
4 min read
union-find graph
Problem#
There are stones placed on a grid at given coordinates. A stone can be removed if it shares a row or column with another remaining stone. Return the maximum number of stones you can remove.
Examples#
stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]→5.stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]→3.
Constraints#
1 <= stones.length <= 1000,0 <= stones[i].x, stones[i].y <= 10^4.
Hints#
Hint 1
In each connected component, you can remove all but one stone.
Hint 2
Answer =
n - #components. Union stones sharing a row or column. Approach#
Treat each row and each column as a DSU node (offset columns by +10000 to avoid collision). Union each stone’s row with its column. The answer is n - #distinct roots.
Solution#
class Solution { unordered_map<int,int> parent; int find(int x) { if (!parent.count(x)) parent[x] = x; while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; } void unite(int a, int b) { parent[find(a)] = find(b); }public: int removeStones(vector<vector<int>>& stones) { for (auto& s : stones) unite(s[0], s[1] + 10001); unordered_set<int> roots; for (auto& s : stones) roots.insert(find(s[0])); return stones.size() - roots.size(); }};func removeStones(stones [][]int) int { parent := map[int]int{} var find func(int) int find = func(x int) int { if _, ok := parent[x]; !ok { parent[x] = x } for parent[x] != x { parent[x] = parent[parent[x]] x = parent[x] } return x } unite := func(a, b int) { parent[find(a)] = find(b) } for _, s := range stones { unite(s[0], s[1]+10001) } roots := map[int]bool{} for _, s := range stones { roots[find(s[0])] = true } return len(stones) - len(roots)}from typing import List
class Solution: def removeStones(self, stones: List[List[int]]) -> int: parent: dict[int, int] = {}
def find(x: int) -> int: if x not in parent: parent[x] = x while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x
def unite(a: int, b: int) -> None: parent[find(a)] = find(b)
for r, c in stones: unite(r, c + 10001) roots = {find(r) for r, _ in stones} return len(stones) - len(roots)function removeStones(stones) { const parent = new Map(); const find = (x) => { if (!parent.has(x)) parent.set(x, x); while (parent.get(x) !== x) { parent.set(x, parent.get(parent.get(x))); x = parent.get(x); } return x; }; const unite = (a, b) => { parent.set(find(a), find(b)); }; for (const [r, c] of stones) unite(r, c + 10001); const roots = new Set(); for (const [r] of stones) roots.add(find(r)); return stones.length - roots.size;}class Solution { private Map<Integer, Integer> parent = new HashMap<>();
public int removeStones(int[][] stones) { for (int[] s : stones) unite(s[0], s[1] + 10001); Set<Integer> roots = new HashSet<>(); for (int[] s : stones) roots.add(find(s[0])); return stones.length - roots.size(); }
private int find(int x) { if (!parent.containsKey(x)) parent.put(x, x); while (parent.get(x) != x) { parent.put(x, parent.get(parent.get(x))); x = parent.get(x); } return x; }
private void unite(int a, int b) { parent.put(find(a), find(b)); }}function removeStones(stones: number[][]): number { const parent = new Map<number, number>(); const find = (x: number): number => { if (!parent.has(x)) parent.set(x, x); while (parent.get(x)! !== x) { parent.set(x, parent.get(parent.get(x)!)!); x = parent.get(x)!; } return x; }; const unite = (a: number, b: number): void => { parent.set(find(a), find(b)); }; for (const [r, c] of stones) unite(r, c + 10001); const roots = new Set<number>(); for (const [r] of stones) roots.add(find(r)); return stones.length - roots.size;}Editorial#
The clever model: union the row index with the column index (offset). Two stones end up in the same component iff a chain of row/column shares connects them. In any component of k stones we can remove k - 1 (peel off all but the last). Total removable = n - #components.
Complexity#
- Time: O(N * α(N)).
- Space: O(N).
Concept revision#
Related#