Created
February 17, 2017 20:50
-
-
Save jianminchen/68a31f9d20dab1658286fbfec7e4fcdb to your computer and use it in GitHub Desktop.
Leetcode 317 - prepare to code review, learn to apply S.O.L.I.D. principles if possible
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
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace Leetcode317_ShortestDistanceFromAllBuildings | |
{ | |
/* | |
* Leetcode 317: | |
* You want to build a house on an empty land which reaches all buildings in the shortest amount | |
* of distance. You can only move up, down, left and right. You are given a 2D grid of values 0, 1 or 2, | |
* where: | |
Each 0 marks an empty land which you can pass by freely. | |
Each 1 marks a building which you cannot pass through. | |
Each 2 marks an obstacle which you cannot pass through. | |
For example, given three buildings at (0,0), (0,4), (2,2), and an obstacle at (0,2): | |
* | |
1 - 0 - 2 - 0 - 1 | |
| | | | | | |
0 - 0 - 0 - 0 - 0 | |
| | | | | | |
0 - 0 - 1 - 0 - 0 | |
* | |
* The point (1,2) is an ideal empty land to build a house, as the total travel distance of 3+3+1=7 is | |
* minimal. So return 7. | |
* | |
* | |
* Analysis from the above blog: | |
* Note: | |
There will be at least one building. If it is not possible to build such house according to | |
the above rules, return -1. | |
* | |
Understand the problem: | |
Use breadth first search to visit all emtpy lands if possible starting from each building, track the statistics. | |
* | |
* Search from each building and calculate the distance to the building. | |
* To find an empty land which can be reachable started from any building. | |
* | |
* | |
*/ | |
public interface IPlace | |
{ | |
string GetKey(int row, int col); | |
int[] DecryptKey(string s); | |
} | |
class WalkTrackingNode : IPlace | |
{ | |
public string Key { get; set; } | |
public int Distance { get; set; } | |
public WalkTrackingNode(string s, int value = 0) | |
{ | |
Key = s; | |
Distance = value; | |
} | |
// interface methods | |
public string GetKey(int row, int col) | |
{ | |
return KeyUsingDash.Encrypt(row, col); | |
} | |
public int[] DecryptKey(string s) | |
{ | |
return KeyUsingDash.Decrypt(s); | |
} | |
public Tuple<int, int> DecryptKeyTuple(string s) | |
{ | |
return KeyUsingDash.DecryptToTuple(s); | |
} | |
} | |
/* | |
* Design to help track distance between building and empty lande | |
* Each building has a key generated by row, col's value, like "0-0" | |
* "0-0" can be decrypted to a node in jagged array two indexes, row, column. | |
*/ | |
public static class KeyUsingDash | |
{ | |
public static string Encrypt(int row, int col) | |
{ | |
return row + "-" + col; | |
} | |
public static int[] Decrypt(string s) | |
{ | |
return Array.ConvertAll(s.Split('-'), int.Parse); | |
} | |
public static Tuple<int, int> DecryptToTuple(string s) | |
{ | |
if (s == null || s.Length < 2) | |
{ | |
return new Tuple<int, int>(-1, -1); | |
} | |
int[] data = Array.ConvertAll(s.Split('-'), int.Parse); | |
return new Tuple<int, int>(data[0], data[1]); | |
} | |
} | |
class EmptyLandAndBuilding | |
{ | |
public string BuildingKey { get; set; } | |
public string LandKey { get; set; } | |
public EmptyLandAndBuilding(Place building, Place land) | |
{ | |
BuildingKey = building.key; | |
LandKey = land.key; | |
} | |
} | |
class Place : IPlace | |
{ | |
public string key { get; set; } | |
public string GetKey(int row, int col) | |
{ | |
return KeyUsingDash.Encrypt(row, col); | |
} | |
public int[] DecryptKey(string s) | |
{ | |
return KeyUsingDash.Decrypt(s); | |
} | |
public Tuple<int,int> DecryptKeyTuple(string s) | |
{ | |
return KeyUsingDash.DecryptToTuple(s); | |
} | |
public Place(int row, int col) | |
{ | |
key = row + "-" + col; | |
} | |
public static bool IsSamePlace(Tuple<int, int> land1, Tuple<int, int> land2) | |
{ | |
return land1.Item1 == land2.Item1 && land1.Item2 == land2.Item2; | |
} | |
/* | |
* 0 - empty land | |
* 1 - building | |
* 2 - obstacle | |
*/ | |
public static IList<Place> GetAllBuildings(int[][] grid) | |
{ | |
var buildings = new List<Place>(); | |
int rows = grid.Length; | |
int cols = grid[0].Length; | |
for (int row = 0; row < rows; row++) | |
{ | |
for (int col = 0; col < cols; col++) | |
{ | |
if (grid[row][col] == 1) | |
{ | |
buildings.Add(new Place(row, col)); | |
} | |
} | |
} | |
return buildings; | |
} | |
} | |
class Solution | |
{ | |
private static readonly int EMTPYLAND = 0; | |
/* | |
* Find an emtpy land to have the shortest distance to all the buildings. | |
* | |
* Solve the problem in the following steps: | |
* 1. Enumerate all buildings, from each building walk 4 directions to reach all | |
* empty lands using breadth first search. Count the visited for each empty land. | |
* 2. Find those empty lands which can be reached by all buildings, calculate the | |
* minimum sum of distance. | |
* | |
*/ | |
public int CalculateShortestDistance(int[][] grid, int rows, int cols) | |
{ | |
var buildings = Place.GetAllBuildings(grid); | |
var emptyLandVisitedCount = new Dictionary<Tuple<int, int>, int>(); | |
var walksFromEachBuilding = new List<Dictionary<EmptyLandAndBuilding, int>>(); | |
foreach (Place building in buildings) | |
{ | |
walksFromEachBuilding.Add(WalkFromBuildingBFS(building, grid, emptyLandVisitedCount)); | |
} | |
// step 2: caluclate the minimum distance | |
int minimumDistance = Int16.MaxValue; | |
foreach (var chosen in emptyLandVisitedCount.Where(x => x.Value == buildings.Count)) | |
{ | |
var emtpyLand = chosen.Key; | |
int sumDistance = 0; | |
foreach (var walk in walksFromEachBuilding) | |
{ | |
foreach (var buildingAndLand in walk) | |
{ | |
var distanceKey = buildingAndLand.Key; | |
var value = buildingAndLand.Value; | |
if (Place.IsSamePlace(emtpyLand, KeyUsingDash.DecryptToTuple(distanceKey.LandKey))) | |
{ | |
sumDistance += value; | |
} | |
} | |
} | |
minimumDistance = (sumDistance < minimumDistance) ? sumDistance : minimumDistance; | |
} | |
return minimumDistance == Int16.MaxValue ? -1 : minimumDistance; | |
} | |
/* | |
* @building - start a walk from building, using BFS | |
* @grid - places including empty land, building, obstacle. n x m size | |
* @emptyLandReached - record visited count for each empty land, | |
* add a new record for a new visit, increment value 1 if the value is existed | |
* | |
* return Dictionary<EmptyLandAndBuilding, int> | |
* distance from building to emtpy land, | |
* Add this enhanced API, test how good I can write BFS algorithm, dealing with multiple | |
* directions. Easy to troubleshoot errors through development. | |
* Building's key value is recorded, and empty land's key value is also recorded, | |
* both are stored in the key object. | |
* distance is the integer value. | |
*/ | |
public new Dictionary<EmptyLandAndBuilding, int> WalkFromBuildingBFS( | |
Place building, | |
int[][] grid, | |
Dictionary<Tuple<int, int>, int> emptyLandReached | |
) | |
{ | |
var distanceData = new Dictionary<EmptyLandAndBuilding, int>(); | |
int rows = grid.Length; | |
int cols = grid[0].Length; | |
var visited = new bool[rows, cols]; | |
var queue = new Queue<WalkTrackingNode>(); | |
// add first node into the queue | |
queue.Enqueue(new WalkTrackingNode(building.key, 0)); | |
// BFS - visit all nodes in the grid | |
while (queue.Count > 0) | |
{ | |
WalkTrackingNode node = queue.Dequeue(); | |
int distance = node.Distance; | |
int[] data = KeyUsingDash.Decrypt(node.Key); | |
int row = data[0]; | |
int col = data[1]; | |
// four directions - left, right, down, up | |
int[][] directions = {new int[2]{-1, 0}, | |
new int[2]{ 1, 0}, | |
new int[2]{ 0, -1}, | |
new int[2]{ 0, 1} | |
}; | |
// increment distance once for all directions, should not be put in | |
// direction loop. Fix the bug in my first writing, remove out of for loop. | |
distance++; | |
for (int i = 0; i < directions.Length; i++) | |
{ | |
int nextRow = row + directions[i][0]; | |
int nextCol = col + directions[i][1]; | |
if (boundaryCheck(nextRow, nextCol, rows, cols) && | |
!visited[nextRow, nextCol] && | |
grid[nextRow][nextCol] == EMTPYLAND) | |
{ | |
var keyObject = new Tuple<int, int>(nextRow, nextCol); | |
if (emptyLandReached.ContainsKey(keyObject)) | |
{ | |
emptyLandReached[keyObject]++; | |
} | |
else | |
{ | |
emptyLandReached.Add(keyObject, 1); | |
} | |
var emptyLand = new Place(nextRow, nextCol); | |
distanceData.Add(new EmptyLandAndBuilding(building, emptyLand), distance); | |
visited[nextRow, nextCol] = true; | |
queue.Enqueue(new WalkTrackingNode(KeyUsingDash.Encrypt(nextRow, nextCol), distance)); | |
} | |
} | |
} | |
return distanceData; | |
} | |
private bool boundaryCheck(int row, int col, int rows, int cols) | |
{ | |
return row >= 0 && row < rows && col >= 0 && col < cols; | |
} | |
/* | |
* Test case: | |
* | |
1 - 0 - 2 - 0 - 1 | |
| | | | | | |
0 - 0 - 0 - 0 - 0 | |
| | | | | | |
0 - 0 - 1 - 0 - 0 | |
* | |
The point (1,2) is an ideal empty land to build a house, | |
as the total travel distance of 3+3+1=7 is minimal. So return 7. | |
* | |
* Ref: jagged array initilization lookup | |
* https://msdn.microsoft.com/en-us/library/2s05feca.aspx | |
*/ | |
static void Main(string[] args) | |
{ | |
RunTestcaseOnWalk(); | |
RunSampleTestcase(); | |
} | |
/* | |
* It is worth time to test API first: | |
* WalkFromBuildingBFS | |
* And choose two points to verify the distance is correct. | |
* BFS is tested, all directions are tested as well. | |
* After that, the shortest distance from all buildings will be much easy | |
* to be tested. | |
*/ | |
public static void RunTestcaseOnWalk() | |
{ | |
int[][] grid = new int[3][] { | |
new int[]{ 1, 0, 2, 0, 1 }, | |
new int[]{ 0, 0, 0, 0, 0 }, | |
new int[]{ 0, 0, 1, 0, 0 } | |
}; | |
Solution solution = new Solution(); | |
var building = new Place(0, 0); | |
var emptyLandStatistics = new Dictionary<Tuple<int, int>, int>(); | |
var distanceData = solution.WalkFromBuildingBFS( | |
building, | |
grid, | |
emptyLandStatistics | |
); | |
// distanceData to count the minimum steps from building top-left corner to | |
// emtpy land bottom-right corner is 6 | |
// one of shortest routes (6 steps): | |
// 1 | |
// 0 0 0 0 0 | |
// 0 | |
// another one of shortest routes (6 steps): | |
// 1 0 | |
// 0 0 0 0 | |
// 0 | |
{ | |
var pair = new EmptyLandAndBuilding(new Place(0, 0), new Place(2, 4)); | |
// manually verify the value | |
//Debug.Assert(distanceData[pair] == 6); | |
} | |
// 0 | |
// 0 0 1 | |
{ | |
var pair = new EmptyLandAndBuilding(new Place(2, 2), new Place(1, 0)); | |
// manually verify the value | |
//Debug.Assert(distanceData[pair] == 3); | |
} | |
} | |
public static void RunSampleTestcase() | |
{ | |
int[][] grid = new int[3][] { | |
new int[]{ 1, 0, 2, 0, 1 }, | |
new int[]{ 0, 0, 0, 0, 0 }, | |
new int[]{ 0, 0, 1, 0, 0 } | |
}; | |
Solution solution = new Solution(); | |
int result = solution.CalculateShortestDistance(grid, 3, 5); | |
Debug.Assert(result == 7); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment