Skip to content

Commit f1a367e

Browse files
committed
Kotlin - Chapter 13: Graphs
* Bipartite Graph Validation * Connect the Dots * Count Islands * Graph Deep Copy * Longest Increasing Path * Matrix Infection * Merging Communities * Prerequisites * Shortest Path * Shortest Transformation Sequence
1 parent 795c6b1 commit f1a367e

11 files changed

+481
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
fun bipartiteGraphValidation(graph: List<List<Int>>): Boolean {
2+
val colors = IntArray(graph.size)
3+
// Determine if each graph component is bipartite.
4+
for (i in graph.indices) {
5+
if (colors[i] == 0 && !dfs(i, 1, graph, colors)) {
6+
return false
7+
}
8+
}
9+
return true
10+
}
11+
12+
fun dfs(node: Int, color: Int, graph: List<List<Int>>, colors: IntArray): Boolean {
13+
colors[node] = color
14+
for (neighbor in graph[node]) {
15+
// If the current neighbor has the same color as the current
16+
// node, the graph is not bipartite.
17+
if (colors[neighbor] == color) {
18+
return false
19+
}
20+
// If the current neighbor is not colored, color it with the
21+
// other color and continue the DFS.
22+
if (colors[neighbor] == 0 && !dfs(neighbor, -color, graph, colors)) {
23+
return false
24+
}
25+
}
26+
return true
27+
}

kotlin/Graphs/ConnectTheDots.kt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
class UnionFind(size: Int) {
2+
private val parent = IntArray(size) { it }
3+
private val size = IntArray(size) { 1 }
4+
5+
fun union(x: Int, y: Int): Boolean {
6+
var repX = find(x)
7+
var repY = find(y)
8+
if (repX != repY) {
9+
if (size[repX] > size[repY]) {
10+
parent[repY] = repX
11+
size[repX] += size[repY]
12+
} else {
13+
parent[repX] = repY
14+
size[repY] += size[repX]
15+
}
16+
// Return true if both groups were merged.
17+
return true
18+
}
19+
// Return false if the points belong to the same group.
20+
return false
21+
}
22+
23+
fun find(x: Int): Int {
24+
if (x == parent[x]) {
25+
return x
26+
}
27+
parent[x] = find(parent[x])
28+
return parent[x]
29+
}
30+
}
31+
32+
fun connectTheDots(points: List<List<Int>>): Int {
33+
val n = points.size
34+
// Create and populate a list of all possible edges.
35+
val edges = mutableListOf<Triple<Int, Int, Int>>()
36+
for (i in 0 until n) {
37+
for (j in i + 1 until n) {
38+
// Manhattan distance.
39+
val cost =
40+
(Math.abs(points[i][0] - points[j][0]) + Math.abs(points[i][1] - points[j][1]))
41+
edges.add(Triple(cost, i, j))
42+
}
43+
}
44+
// Sort the edges by their cost in ascending order.
45+
edges.sortBy { it.first }
46+
val uf = UnionFind(n)
47+
var totalCost = 0
48+
var edgesAdded = 0
49+
// Use Kruskal's algorithm to create the MST and identify its minimum cost.
50+
for ((cost, p1, p2) in edges) {
51+
// If the points are not already connected (i.e. their representatives are
52+
// not the same), connect them, and add the cost to the total cost.
53+
if (uf.union(p1, p2)) {
54+
totalCost += cost
55+
edgesAdded++
56+
// If n - 1 edges have been added to the MST, the MST is complete.
57+
if (edgesAdded == n - 1) {
58+
return totalCost
59+
}
60+
}
61+
}
62+
return totalCost
63+
}

kotlin/Graphs/CountIslands.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
fun countIslands(matrix: MutableList<MutableList<Int>>): Int {
2+
if (matrix.isEmpty()) {
3+
return 0
4+
}
5+
var count = 0
6+
for (r in matrix.indices) {
7+
for (c in matrix[0].indices) {
8+
// If a land cell is found, perform DFS to explore the full
9+
// island, and include this island in our count.
10+
if (matrix[r][c] == 1) {
11+
dfs(r, c, matrix)
12+
count++
13+
}
14+
}
15+
}
16+
return count
17+
}
18+
19+
fun dfs(r: Int, c: Int, matrix: MutableList<MutableList<Int>>) {
20+
// Mark the current land cell as visited.
21+
matrix[r][c] = -1
22+
// Define direction vectors for up, down, left, and right.
23+
val dirs = listOf(Pair(-1, 0), Pair(1, 0), Pair(0, -1), Pair(0, 1))
24+
// Recursively call DFS on each neighboring land cell to continue
25+
// exploring this island.
26+
for (d in dirs) {
27+
val nextR = r + d.first
28+
val nextC = c + d.second
29+
if (isWithinBounds(nextR, nextC, matrix) && matrix[nextR][nextC] == 1) {
30+
dfs(nextR, nextC, matrix)
31+
}
32+
}
33+
}
34+
35+
fun isWithinBounds(r: Int, c: Int, matrix: MutableList<MutableList<Int>>): Boolean {
36+
return r in matrix.indices && c in matrix[0].indices
37+
}

kotlin/Graphs/GraphDeepCopy.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import ds.GraphNode
2+
3+
/*
4+
Definition of GraphNode:
5+
data class GraphNode(
6+
val value: Int,
7+
val neighbors: MutableList<GraphNode> = mutableListOf()
8+
)
9+
*/
10+
11+
fun graphDeepCopy(node: GraphNode?): GraphNode? {
12+
if (node == null) {
13+
return null
14+
}
15+
return dfs(node, mutableMapOf())
16+
}
17+
18+
fun dfs(node: GraphNode, cloneMap: MutableMap<GraphNode, GraphNode>): GraphNode {
19+
// If this node was already cloned, then return this previously
20+
// cloned node.
21+
if (node in cloneMap) {
22+
return cloneMap[node]!!
23+
}
24+
// Clone the current node.
25+
val clonedNode = GraphNode(node.value)
26+
// Store the current clone to ensure it doesn't need to be created
27+
// again in future DFS calls.
28+
cloneMap[node] = clonedNode
29+
// Iterate through the neighbors of the current node to connect
30+
// their clones to the current cloned node.
31+
for (neighbor in node.neighbors) {
32+
val clonedNeighbor = dfs(neighbor, cloneMap)
33+
clonedNode.neighbors.add(clonedNeighbor)
34+
}
35+
return clonedNode
36+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
fun longestIncreasingPath(matrix: List<List<Int>>): Int {
2+
if (matrix.isEmpty()) {
3+
return 0
4+
}
5+
var res = 0
6+
val m = matrix.size
7+
val n = matrix[0].size
8+
val memo = Array(m) { IntArray(n) }
9+
// Find the longest increasing path starting at each cell. The
10+
// maximum of these is equal to the overall longest increasing
11+
// path.
12+
for (r in matrix.indices) {
13+
for (c in matrix[0].indices) {
14+
res = maxOf(res, dfs(r, c, matrix, memo))
15+
}
16+
}
17+
return res
18+
}
19+
20+
fun dfs(r: Int, c: Int, matrix: List<List<Int>>, memo: Array<IntArray>): Int {
21+
if (memo[r][c] != 0) {
22+
return memo[r][c]
23+
}
24+
var maxPath = 1
25+
val dirs = listOf(-1 to 0, 1 to 0, 0 to -1, 0 to 1)
26+
// The longest path starting at the current cell is equal to the
27+
// longest path of its larger neighboring cells, plus 1.
28+
for (d in dirs) {
29+
val (nextR, nextC) = r + d.first to c + d.second
30+
if (isWithinBounds(nextR, nextC, matrix) && matrix[nextR][nextC] > matrix[r][c]) {
31+
maxPath = maxOf(maxPath, 1 + dfs(nextR, nextC, matrix, memo))
32+
}
33+
}
34+
memo[r][c] = maxPath
35+
return maxPath
36+
}
37+
38+
fun isWithinBounds(r: Int, c: Int, matrix: List<List<Int>>): Boolean {
39+
return r in matrix.indices && c in matrix[0].indices
40+
}

kotlin/Graphs/MatrixInfection.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
fun matrixInfection(matrix: MutableList<MutableList<Int>>): Int {
2+
val dirs = listOf(-1 to 0, 1 to 0, 0 to -1, 0 to 1)
3+
val queue = ArrayDeque<Pair<Int, Int>>()
4+
var ones = 0
5+
var seconds = 0
6+
// Count the total number of uninfected cells and add each infected
7+
// cell to the queue to represent level 0 of the level-order
8+
// traversal.
9+
for (r in matrix.indices) {
10+
for (c in matrix[0].indices) {
11+
if (matrix[r][c] == 1) {
12+
ones++
13+
} else if (matrix[r][c] == 2) {
14+
queue.add(r to c)
15+
}
16+
}
17+
}
18+
// Use level-order traversal to determine how long it takes to
19+
// infect the uninfected cells.
20+
while (queue.isNotEmpty() && ones > 0) {
21+
// 1 second passes with each level of the matrix that's explored.
22+
seconds++
23+
for (i in 0 until queue.size) {
24+
val (r, c) = queue.removeFirst()
25+
// Infect any neighboring 1s and add them to the queue to be
26+
// processed in the next level.
27+
for ((dr, dc) in dirs) {
28+
val nextR = r + dr
29+
val nextC = c + dc
30+
if (isWithinBounds(nextR, nextC, matrix) && matrix[nextR][nextC] == 1) {
31+
matrix[nextR][nextC] = 2
32+
ones--
33+
queue.add(nextR to nextC)
34+
}
35+
}
36+
}
37+
}
38+
// If there are still uninfected cells left, return -1. Otherwise,
39+
// return the time passed.
40+
return if (ones == 0) {
41+
seconds
42+
} else {
43+
-1
44+
}
45+
}
46+
47+
fun isWithinBounds(r: Int, c: Int, matrix: List<List<Int>>): Boolean {
48+
return r in matrix.indices && c in matrix[0].indices
49+
}

kotlin/Graphs/MergingCommunities.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
class UnionFind(size: Int) {
2+
3+
private val parent = IntArray(size) { it }
4+
private val size = IntArray(size) { 1 }
5+
6+
// With comment
7+
fun union(x: Int, y: Int) {
8+
val repX = find(x)
9+
val repY = find(y)
10+
if (repX != repY) {
11+
// If 'repX' represents a larger community, connect
12+
// 'repY 's community to it.
13+
if (size[repX] > size[repY]) {
14+
parent[repY] = repX
15+
size[repX] += size[repY]
16+
} else {
17+
// Otherwise, connect 'repX's community to 'repY'.
18+
parent[repX] = repY
19+
size[repY] += size[repX]
20+
}
21+
}
22+
}
23+
24+
fun find(x: Int): Int {
25+
if (x == parent[x]) {
26+
return x
27+
}
28+
// Path compression.
29+
parent[x] = find(parent[x])
30+
return parent[x]
31+
}
32+
33+
fun getSize(x: Int): Int {
34+
return size[find(x)]
35+
}
36+
}
37+
38+
class MergingCommunities(n: Int) {
39+
40+
private val uf = UnionFind(n)
41+
42+
fun connect(x: Int, y: Int) {
43+
uf.union(x, y)
44+
}
45+
46+
fun getCommunitySize(x: Int): Int {
47+
return uf.getSize(x)
48+
}
49+
}

kotlin/Graphs/Prerequisites.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
fun prerequisites(n: Int, prerequisites: List<List<Int>>): Boolean {
2+
val graph = mutableMapOf<Int, MutableList<Int>>()
3+
val inDegrees = IntArray(n)
4+
// Represent the graph as an adjacency list and record the in-
5+
// degree of each course.
6+
for ((prerequisite, course) in prerequisites) {
7+
graph.getOrPut(prerequisite) { mutableListOf() }.add(course)
8+
inDegrees[course]++
9+
}
10+
val queue = ArrayDeque<Int>()
11+
// Add all courses with an in-degree of 0 to the queue.
12+
for (i in inDegrees.indices) {
13+
if (inDegrees[i] == 0) {
14+
queue.add(i)
15+
}
16+
}
17+
var enrolledCourses = 0
18+
// Perform topological sort.
19+
while (queue.isNotEmpty()) {
20+
val node = queue.removeFirst()
21+
enrolledCourses++
22+
for (neighbor in graph[node] ?: mutableListOf()) {
23+
inDegrees[neighbor]--
24+
// If the in-degree of a neighboring course becomes 0, add
25+
// it to the queue.
26+
if (inDegrees[neighbor] == 0) {
27+
queue.add(neighbor)
28+
}
29+
}
30+
}
31+
// Return true if we've successfully enrolled in all courses.
32+
return enrolledCourses == n
33+
}

kotlin/Graphs/ShortestPath.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import java.util.PriorityQueue
2+
3+
fun shortestPath(n: Int, edges: List<List<Int>>, start: Int): List<Int> {
4+
val graph = hashMapOf<Int, MutableList<Pair<Int, Int>>>()
5+
val distances = MutableList(n) { Int.MAX_VALUE }
6+
distances[start] = 0
7+
// Represent the graph as an adjacency list.
8+
for ((u, v, w) in edges) {
9+
graph.computeIfAbsent(u) { mutableListOf() }.add(v to w)
10+
graph.computeIfAbsent(v) { mutableListOf() }.add(u to w)
11+
}
12+
val minHeap = PriorityQueue<Pair<Int, Int>>(compareBy { it.first })
13+
minHeap.add(0 to start) // (distance, node)
14+
// Use Dijkstra's algorithm to find the shortest path between the start node
15+
// and all other nodes.
16+
while (minHeap.isNotEmpty()) {
17+
val (currDist, currNode) = minHeap.poll()
18+
// If the current distance to this node is greater than the recorded
19+
// distance, we've already found the shortest distance to this node.
20+
if (currDist > distances[currNode]) {
21+
continue
22+
}
23+
// Update the distances of the neighboring nodes.
24+
for ((neighbor, weight) in graph[currNode] ?: emptyList()) {
25+
val neighborDist = currDist + weight
26+
// Only update the distance if we find a shorter path to this
27+
// neighbor.
28+
if (neighborDist < distances[neighbor]) {
29+
distances[neighbor] = neighborDist
30+
minHeap.add(neighborDist to neighbor)
31+
}
32+
}
33+
}
34+
// Convert all infinity values to -1, representing unreachable nodes.
35+
return distances.map { if (it == Int.MAX_VALUE) -1 else it }
36+
}

0 commit comments

Comments
 (0)