🧠
Union-Find (Disjoint Set) with Sorting Cells by Elevation
💡 This approach uses union-find to dynamically connect cells as water rises, efficiently checking when start and end become connected.
Intuition
Sort all cells by elevation. Starting from the lowest elevation, union adjacent cells as water level rises. The earliest time when start and end are connected is the answer.
Algorithm
- Create a list of all cells with their elevations.
- Sort the list by elevation ascending.
- Initialize union-find data structure for all cells.
- Iterate over cells in sorted order, union with adjacent cells already processed.
- After each union, check if start and end are connected; if yes, return current elevation.
💡 The key is to realize that connectivity changes only when water level reaches a cell's elevation, so processing in sorted order is optimal.
from typing import List
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]]
x = self.parent[x]
return x
def union(self, x, y):
rx, ry = self.find(x), self.find(y)
if rx == ry:
return False
if self.rank[rx] < self.rank[ry]:
self.parent[rx] = ry
elif self.rank[ry] < self.rank[rx]:
self.parent[ry] = rx
else:
self.parent[ry] = rx
self.rank[rx] += 1
return True
def swimInWater(grid: List[List[int]]) -> int:
n = len(grid)
uf = UnionFind(n * n)
cells = [(grid[i][j], i, j) for i in range(n) for j in range(n)]
cells.sort()
visited = [[False]*n for _ in range(n)]
def index(x, y):
return x * n + y
directions = [(1,0), (-1,0), (0,1), (0,-1)]
for elevation, x, y in cells:
visited[x][y] = True
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < n and 0 <= ny < n and visited[nx][ny]:
uf.union(index(x,y), index(nx, ny))
if uf.find(0) == uf.find(n*n - 1):
return elevation
# Driver code
if __name__ == '__main__':
grid = [[0,2],[1,3]]
print(swimInWater(grid)) # Expected output: 3
Line Notes
class UnionFind:Defines union-find data structure for efficient connectivity tracking among cells.
cells.sort()Sort cells by elevation to simulate water rising in ascending order.
visited[x][y] = TrueMark cell as flooded at current elevation, enabling union with neighbors.
if uf.find(0) == uf.find(n*n - 1):Check if start and end cells are connected, indicating path availability.
import java.util.*;
public class SwimInRisingWater {
static class UnionFind {
int[] parent, rank;
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
}
public int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
public boolean union(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry) return false;
if (rank[rx] < rank[ry]) parent[rx] = ry;
else if (rank[ry] < rank[rx]) parent[ry] = rx;
else {
parent[ry] = rx;
rank[rx]++;
}
return true;
}
}
public int swimInWater(int[][] grid) {
int n = grid.length;
UnionFind uf = new UnionFind(n * n);
int[][] cells = new int[n * n][3];
int idx = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cells[idx++] = new int[]{grid[i][j], i, j};
}
}
Arrays.sort(cells, Comparator.comparingInt(a -> a[0]));
boolean[][] visited = new boolean[n][n];
int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}};
for (int[] cell : cells) {
int elevation = cell[0], x = cell[1], y = cell[2];
visited[x][y] = true;
for (int[] d : directions) {
int nx = x + d[0], ny = y + d[1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && visited[nx][ny]) {
uf.union(x * n + y, nx * n + ny);
}
}
if (uf.find(0) == uf.find(n * n - 1)) return elevation;
}
return -1;
}
public static void main(String[] args) {
SwimInRisingWater sol = new SwimInRisingWater();
int[][] grid = {{0, 2}, {1, 3}};
System.out.println(sol.swimInWater(grid)); // Expected: 3
}
}
Line Notes
class UnionFindUnion-find data structure to track connectivity efficiently among grid cells.
Arrays.sort(cells, Comparator.comparingInt(a -> a[0]));Sort cells by elevation ascending to simulate water rising order.
visited[x][y] = true;Mark cell flooded at current elevation to enable union with neighbors.
if (uf.find(0) == uf.find(n * n - 1)) return elevation;Check if start and end are connected, indicating path availability.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class UnionFind {
vector<int> parent, rank;
public:
UnionFind(int n) : parent(n), rank(n, 0) {
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
bool unionSet(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry) return false;
if (rank[rx] < rank[ry]) parent[rx] = ry;
else if (rank[ry] < rank[rx]) parent[ry] = rx;
else {
parent[ry] = rx;
rank[rx]++;
}
return true;
}
};
int swimInWater(vector<vector<int>>& grid) {
int n = grid.size();
UnionFind uf(n * n);
vector<tuple<int,int,int>> cells;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cells.emplace_back(grid[i][j], i, j);
}
}
sort(cells.begin(), cells.end());
vector<vector<bool>> visited(n, vector<bool>(n, false));
int directions[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
for (auto& [elevation, x, y] : cells) {
visited[x][y] = true;
for (auto& d : directions) {
int nx = x + d[0], ny = y + d[1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && visited[nx][ny]) {
uf.unionSet(x * n + y, nx * n + ny);
}
}
if (uf.find(0) == uf.find(n * n - 1)) return elevation;
}
return -1;
}
int main() {
vector<vector<int>> grid = {{0, 2}, {1, 3}};
cout << swimInWater(grid) << endl; // Expected: 3
return 0;
}
Line Notes
UnionFind uf(n * n);Initialize union-find for all cells to track connectivity.
sort(cells.begin(), cells.end());Sort cells by elevation ascending to simulate water rising order.
visited[x][y] = true;Mark cell flooded at current elevation to enable union with neighbors.
if (uf.find(0) == uf.find(n * n - 1)) return elevation;Check connectivity of start and end cells to determine earliest swim time.
class UnionFind {
constructor(n) {
this.parent = Array.from({length: n}, (_, i) => i);
this.rank = Array(n).fill(0);
}
find(x) {
while (this.parent[x] !== x) {
this.parent[x] = this.parent[this.parent[x]];
x = this.parent[x];
}
return x;
}
union(x, y) {
let rx = this.find(x), ry = this.find(y);
if (rx === ry) return false;
if (this.rank[rx] < this.rank[ry]) this.parent[rx] = ry;
else if (this.rank[ry] < this.rank[rx]) this.parent[ry] = rx;
else {
this.parent[ry] = rx;
this.rank[rx]++;
}
return true;
}
}
function swimInWater(grid) {
const n = grid.length;
const uf = new UnionFind(n * n);
const cells = [];
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
cells.push([grid[i][j], i, j]);
}
}
cells.sort((a,b) => a[0] - b[0]);
const visited = Array.from({length: n}, () => Array(n).fill(false));
const directions = [[1,0],[-1,0],[0,1],[0,-1]];
for (const [elevation, x, y] of cells) {
visited[x][y] = true;
for (const [dx, dy] of directions) {
const nx = x + dx, ny = y + dy;
if (nx >= 0 && nx < n && ny >= 0 && ny < n && visited[nx][ny]) {
uf.union(x * n + y, nx * n + ny);
}
}
if (uf.find(0) === uf.find(n * n - 1)) return elevation;
}
return -1;
}
// Test
console.log(swimInWater([[0,2],[1,3]])); // Expected: 3
Line Notes
class UnionFindUnion-find data structure for connectivity tracking among grid cells.
cells.sort((a,b) => a[0] - b[0]);Sort cells by elevation ascending to simulate water rising order.
visited[x][y] = true;Mark cell flooded at current elevation to enable union with neighbors.
if (uf.find(0) === uf.find(n * n - 1)) return elevation;Check if start and end cells are connected, indicating path availability.
TimeO(n^2 log n^2) = O(n^2 log n)
SpaceO(n^2)
Sorting all n^2 cells takes O(n^2 log n^2), union-find operations are almost O(1) amortized.
💡 For n=50, sorting 2500 cells is efficient, and union-find operations are very fast, making this approach optimal.
Interview Verdict: Accepted
This is the optimal approach using union-find, often preferred in interviews for its elegance and efficiency.