Skip to content

Instantly share code, notes, and snippets.

@enjoylife
Last active July 18, 2021 09:37
Show Gist options
  • Save enjoylife/aded1d9c3cbb59e0aeed7c3887e25d49 to your computer and use it in GitHub Desktop.
Save enjoylife/aded1d9c3cbb59e0aeed7c3887e25d49 to your computer and use it in GitHub Desktop.
Given a matrix of 0s and 1s, where 0 represents water and 1 represents land, count the number of islands. Two pieces of land are connected if they are vertically or horizontally touching. Two pieces of land that are diagonal from each other are not touching.
// our simple example to play with
let island = [
[1, 1, 0, 0, 0],
[0, 1, 0, 0, 1],
[1, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1]
];
// main driver of our code, which will setup our graph, and provide
// initialization of any recursive calls
function countIslandDFS(island) {
let count = 0;
// Creating the helper Graph interface, see below.
// I would fill that out latter but for now i'll list off the
// high level idea
const graph = new Graph(island);
/*
We will be using a graph traversal algorithm...
either bfs or dfs...
In this problem we need information in an area local to
each node, basically we need to probe the extent of an
islands land mass. Bfs could work but the advantage of BFS
is in the ability to terminate a search early, which doesn't
matter here as we must go through every point no matter what.
So dfs and its easy implementation makes sense
*/
// initialization of a matrix to hold the visited nodes in our graph
const visited = new Array(graph.numRow).fill(false)
.map(() => new Array(graph.numCol).fill(false));
// we need to at least go through each point in our grid to see
// if its either an island or possibly part of a larger island
for (let i = 0; i < graph.numRow; i++) {
for (let j = 0; j < graph.numCol; j++) {
// if we haven't visited this island yet, lets increment our count
// and visit its connected components, "the island mass"
if (!visited[i][j] && graph.matrix[i][j] === 1) {
graph.dfs(i, j, visited);
count++;
}
}
}
return count;
}
function countIslandBFS(island) {
// this is all the same as our dfs version
let count = 0;
const graph = new Graph(island);
const visited = new Array(graph.numRow).fill(false)
.map(() => new Array(graph.numCol).fill(false));
// Now we need a queue for our bfs.
// Of course this is a terrible idea
// performance wise since queue.shift is O(n)
// but this is just a simple example to show
// how bfs is an alternative to dfs
let queue = [];
// same loop structure just call our bfs instead
for (let i = 0; i < graph.numRow; i++) {
for (let j = 0; j < graph.numCol; j++) {
if (!visited[i][j] && graph.matrix[i][j] === 1) {
graph.bfs(i, j, visited, queue);
count++;
}
}
}
return count
}
// I'm encapsulating how we traverse our graph in
// this class so its a little easier to reason about
class Graph {
// i'll save the matrix and compute the dimensions now so I dont have to later
constructor(matrix) {
this.matrix = matrix;
this.numCol = matrix[0].length;
// toy problem, so i'm assuming correctly passed in data,
// therefore i'm conveniently forgoing initialization checks
this.numRow = matrix.length;
}
// To traverse the world our islands inhabit, we must respect its flat world nature, so we avoid going out of bounds with bounds checks we make sure if we are given a node we only
shouldTraverse(i, j, visited) {
// i is our row position
// j is our col position
if ((i < 0 || i > this.numRow - 1)
|| j < 0 || j > this.numCol - 1) {
return false
}
// we shouldn't traverse nodes we already have done
if (visited[i][j] === true) {
return false;
}
// we would want to traverse more only if its part of
// or it's own island
return this.matrix[i][j] === 1
}
// listing out how we will move within our graph from a given node.
static get moves() {
return [
[-1, 0], // middle left
[-1, -1], // top left
[0, -1], // top middle
[1, -1], // top right
[1, 0], // middle right
[1, 1], // bottom right
[0, 1], // bottom middle
[-1, 1] // bottom left
];
}
// our depth first search which respects the 8 degrees of freedom
// we have in moving in our grid
dfs(i, j, visited) {
// dfs depends on marking where we have been
// assume we have created this already which will be initialized on our first call of dfs
visited[i][j] = true;
// For all the possible directions, we try
// and traverse along any nodes which are part of this current island. Using Recursion each time and expand our islands horizon.
Graph.moves.forEach(move => {
const a = i + move[0];
const b = j + move[1];
if (this.shouldTraverse(a, b, visited)) {
this.dfs(a, b, visited);
}
});
}
// bfs version
bfs(i, j, visited, queue) {
visited[i][j] = true;
queue.push([i, j]);
while (queue.length !== 0) {
let node = queue.shift();
Graph.moves.forEach(move => {
const a = node[0] + move[0];
const b = node[1] + move[1];
if (this.shouldTraverse(a, b, visited)) {
this.bfs(a, b, visited, queue);
}
});
}
}
}
console.log(countIslandDFS(island));
console.log(countIslandBFS(island));
@markusait
Copy link

Great stuff! I really liked your solution and comments.
However I beleive there is a small mistake in your code, you swaped columns and rows. Therefore line 89 should be
this.numCol = matrix[0].length;
and
this.numRow = matrix.length;
also one could improove the alogirthm slightly by setting visited[i][j] to true if the line 0.
Thank you ;)

@enjoylife
Copy link
Author

... you swapped columns and rows

Good catch, fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment