Last active
July 18, 2021 09:37
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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)); | |
... 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
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 ;)