diff --git a/day19/README.md b/day19/README.md new file mode 100644 index 0000000..6b4d032 --- /dev/null +++ b/day19/README.md @@ -0,0 +1,72 @@ +question of the day: https://leetcode.com/problems/01-matrix/#/description + +Given a matrix consists of 0 and 1, find the distance of +the nearest 0 for each cell. + +The distance between two adjacent cells is 1. + +*Example 1:* + +Input: + +```ruby +0 0 0 +0 1 0 +0 0 0 +``` + +Output: + +```ruby +0 0 0 +0 1 0 +0 0 0 +``` + +*Example 2:* + +Input: + +```ruby +0 0 0 +0 1 0 +1 1 1 +``` + +Output: + +```ruby +0 0 0 +0 1 0 +1 2 1 +``` + +Assumptions we can make: + +1. The number of elements of the given matrix will not exceed 10,000. +2. There are at least one 0 in the given matrix. +3. The cells are adjacent in only four directions: up, down, left and right. + +## Ideas + +This is like drawing out a contour map. The 0's are the peaks of +hills and mountains, while the parts of the matrix that are far away +from any 0's are like the valleys. + +We can start from each peak and go outwards. It'd be a BFS-like +approach. Do a search through the whole matrix once first and find +where all the 0's are. Add each of those positions into a queue, +and then use that queue to start off a BFS. During this BFS, we +check the neighboring cells to see what the minimal value is that +we can place in this cell. + +This solution is `O(n)` to find the 0s, and then `O(n)` to run the +BFS. My implementation also requires `O(n)` space, although I think +it's possible to modify the matrix in-place with some more clever +checking. + +## Code + +[Ruby](./matrixCountours.rb) + +## Follow up diff --git a/day19/matrixCountours.rb b/day19/matrixCountours.rb new file mode 100644 index 0000000..fe8d923 --- /dev/null +++ b/day19/matrixCountours.rb @@ -0,0 +1,108 @@ +def contourDistances(matrix) + rows, cols = matrix.size, matrix[0].size + + if rows == 0 || cols == 0 + return matrix + end + + contours = Array.new(rows).map { |x| [nil] * cols } + queue = [] + # start by finding 0s + for row in 0..rows-1 + for col in 0..cols-1 + if matrix[row][col] == 0 + queue << [row, col] + contours[row][col] = 0 + end + end + end + + # do bfs + while queue.size > 0 + current = queue.shift + row, col = current + contours[row][col] = getMinNeighbor(contours, row, col) + 1 + for i, j in [[row-1, col], [row, col-1], [row, col+1], [row+1, col]] + if withinBounds([i, j], rows, cols) && contours[i][j] == nil + queue << [i, j] + end + end + end + + contours +end + +def withinBounds(coordinate, rows, cols) + x, y = coordinate + x >= 0 && x < rows && y >= 0 && y < cols +end + +def getMinNeighbor(matrix, row, col) + if matrix[row][col] == 0 + return -1 + end + + vals = [] + rows, cols = matrix.size, matrix[0].size + for i, j in [[row-1, col], [row, col-1], [row, col+1], [row+1, col]] + if withinBounds([i, j], rows, cols) + if matrix[i][j] != nil + vals << matrix[i][j] + end + end + end + + vals.min +end + +######### +# Tests # +######### + +class AssertionError < RuntimeError +end + +def assert &block + raise AssertionError unless yield +end + +def tests + # edge cases + empty = [[]] + emptyOutput = [[]] + assert { contourDistances(empty) == emptyOutput } + + one = [[0]] + oneOutput = [[0]] + assert { contourDistances(one) == oneOutput } + + # other cases + matrix = [[1,1,1], + [1,0,1], + [1,1,1]] + + output = [[2,1,2], + [1,0,1], + [2,1,2]] + assert { contourDistances(matrix) == output } + + matrix1 = [[0,0,0], + [0,1,0], + [0,0,0]] + + output1 = [[0,0,0], + [0,1,0], + [0,0,0]] + assert { contourDistances(matrix1) == output1 } + + matrix2 = [[0,0,0], + [0,1,0], + [1,1,1]] + + output2 = [[0,0,0], + [0,1,0], + [1,2,1]] + assert { contourDistances(matrix2) == output2 } +end + +tests()