diff --git a/java/Graphs/BipartiteGraphValidation.java b/java/Graphs/BipartiteGraphValidation.java new file mode 100644 index 0000000..1a683ea --- /dev/null +++ b/java/Graphs/BipartiteGraphValidation.java @@ -0,0 +1,29 @@ +public class BipartiteGraphValidation { + public boolean bipartiteGraphValidation(int[][] graph) { + int[] colors = new int[graph.length]; + // Determine if each graph component is bipartite. + for (int i = 0; i < graph.length; i++) { + if (colors[i] == 0 && !dfs(i, 1, graph, colors)) { + return false; + } + } + return true; + } + + private boolean dfs(int node, int color, int[][] graph, int[] colors) { + colors[node] = color; + for (int neighbor : graph[node]) { + // If the current neighbor has the same color as the current + // node, the graph is not bipartite. + if (colors[neighbor] == color) { + return false; + } + // If the current neighbor is not colored, color it with the + // other color and continue the DFS. + if (colors[neighbor] == 0 && !dfs(neighbor, -color, graph, colors)) { + return false; + } + } + return true; + } +} diff --git a/java/Graphs/ConnectTheDots.java b/java/Graphs/ConnectTheDots.java new file mode 100644 index 0000000..b543b79 --- /dev/null +++ b/java/Graphs/ConnectTheDots.java @@ -0,0 +1,82 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +class UnionFind { + int[] parent; + int[] size; + + public UnionFind(int size) { + this.parent = new int[size]; + this.size = new int[size]; + for (int i = 0; i < size; i++) { + this.parent[i] = i; + } + Arrays.fill(this.size, 1); + } + + public boolean union(int x, int y) { + int repX = find(x); + int repY = find(y); + if (repX != repY) { + if (this.size[repX] > this.size[repY]) { + this.parent[repY] = repX; + this.size[repX] += this.size[repY]; + } + else { + this.parent[repX] = repY; + this.size[repY] += this.size[repX]; + } + // Return True if both groups were merged. + return true; + } + // Return False if the points belong to the same group. + return false; + } + + public int find(int x) { + if (x == this.parent[x]) { + return x; + } + this.parent[x] = find(this.parent[x]); + return this.parent[x]; + } +} + +public class ConnectTheDots { + public int connectTheDots(int[][] points) { + int n = points.length; + // Create and populate a list of all possible edges. + List edges = new ArrayList<>(); + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + // Manhattan distance. + int cost = Math.abs(points[i][0] - points[j][0]) + Math.abs(points[i][1] - points[j][1]); + edges.add(new int[]{cost, i, j}); + } + } + // Sort the edges by their cost in ascending order. + Collections.sort(edges, (a, b) -> Integer.compare(a[0], b[0])); + UnionFind uf = new UnionFind(n); + int totalCost, edgesAdded; + totalCost = edgesAdded = 0; + // Use Kruskal's algorithm to create the MST and identify its minimum cost. + for (int[] edge : edges) { + int cost = edge[0]; + int p1 = edge[1]; + int p2 = edge[2]; + // If the points are not already connected (i.e. their representatives are + // not the same), connect them, and add the cost to the total cost. + if (uf.union(p1, p2)) { + totalCost += cost; + edgesAdded++; + // If n - 1 edges have been added to the MST, the MST is complete. + if (edgesAdded == n - 1) { + return totalCost; + } + } + } + return 0; + } +} diff --git a/java/Graphs/CountIslands.java b/java/Graphs/CountIslands.java new file mode 100644 index 0000000..87fc024 --- /dev/null +++ b/java/Graphs/CountIslands.java @@ -0,0 +1,39 @@ +public class CountIslands { + public int countIslands(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return 0; + } + int count = 0; + for (int r = 0; r < matrix.length; r++) { + for (int c = 0; c < matrix[0].length; c++) { + // If a land cell is found, perform DFS to explore the full + // island, and include this island in our count. + if (matrix[r][c] == 1) { + dfs(r, c, matrix); + count++; + } + } + } + return count; + } + + private void dfs(int r, int c, int[][] matrix) { + // Mark the current land cell as visited. + matrix[r][c] = -1; + // Define direction vectors for up, down, left, and right. + int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + // Recursively call DFS on each neighboring land cell to continue + // exploring this island. + for (int[] d : dirs) { + int nextR = r + d[0]; + int nextC = c + d[1]; + if (isWithinBounds(nextR, nextC, matrix) && matrix[nextR][nextC] == 1) { + dfs(nextR, nextC, matrix); + } + } + } + + private boolean isWithinBounds(int r, int c, int[][] matrix) { + return 0 <= r && r < matrix.length && 0 <= c && c < matrix[0].length; + } +} diff --git a/java/Graphs/GraphDeepCopy.java b/java/Graphs/GraphDeepCopy.java new file mode 100644 index 0000000..58013ad --- /dev/null +++ b/java/Graphs/GraphDeepCopy.java @@ -0,0 +1,46 @@ +import java.util.HashMap; +import java.util.Map; + +import DS.GraphNode; + +/* + // Definition of GraphNode: + class GraphNode { + public int val; + public List neighbors; + public MultiLevelListNode(int val) { + this.val = val; + this.neighbors = new ArrayList<>(); + } + } + */ + +public class GraphDeepCopy { + public GraphNode graphDeepCopy(GraphNode node) { + if (node == null) { + return null; + } + Map cloneMap = new HashMap<>(); + return dfs(node, cloneMap); + } + + private GraphNode dfs(GraphNode node, Map cloneMap) { + // If this node was already cloned, then return this previously + // cloned node. + if (cloneMap.containsKey(node)) { + return cloneMap.get(node); + } + // Clone the current node. + GraphNode clonedNode = new GraphNode(node.val); + // Store the current clone to ensure it doesn't need to be created + // again in future DFS calls. + cloneMap.put(node, clonedNode); + // Iterate through the neighbors of the current node to connect + // their clones to the current cloned node. + for (GraphNode neighbor : node.neighbors) { + GraphNode clonedNeighbor = dfs(neighbor, cloneMap); + clonedNode.neighbors.add(clonedNeighbor); + } + return clonedNode; + } +} diff --git a/java/Graphs/LongestIncreasingPath.java b/java/Graphs/LongestIncreasingPath.java new file mode 100644 index 0000000..07c7147 --- /dev/null +++ b/java/Graphs/LongestIncreasingPath.java @@ -0,0 +1,43 @@ +public class LongestIncreasingPath { + public int longestIncreasingPath(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return 0; + } + int res = 0; + int m = matrix.length; + int n = matrix[0].length; + int[][] memo = new int[m][n]; + // Find the longest increasing path starting at each cell. The + // maximum of these is equal to the overall longest increasing + // path. + for (int r = 0; r < m; r++) { + for (int c = 0; c < n; c++) { + res = Math.max(res, dfs(r, c, matrix, memo)); + } + } + return res; + } + + private int dfs(int r, int c, int[][] matrix, int[][] memo) { + if (memo[r][c] != 0) { + return memo[r][c]; + } + int maxPath = 1; + int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + // The longest path starting at the current cell is equal to the + // longest path of its larger neighboring cells, plus 1. + for (int[] d : dirs) { + int nextR = r + d[0]; + int nextC = c + d[1]; + if (isWithinBounds(nextR, nextC, matrix) && matrix[nextR][nextC] > matrix[r][c]) { + maxPath = Math.max(maxPath, 1 + dfs(nextR, nextC, matrix, memo)); + } + } + memo[r][c] = maxPath; + return maxPath; + } + + private boolean isWithinBounds(int r, int c, int[][] matrix) { + return 0 <= r && r < matrix.length && 0 <= c && c < matrix[0].length; + } +} diff --git a/java/Graphs/MatrixInfection.java b/java/Graphs/MatrixInfection.java new file mode 100644 index 0000000..b0b2f5a --- /dev/null +++ b/java/Graphs/MatrixInfection.java @@ -0,0 +1,53 @@ +import java.util.LinkedList; +import java.util.Queue; + +public class MatrixInfection { + public int matrixInfection(int[][] matrix) { + int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + Queue queue = new LinkedList<>(); + int ones, seconds; + ones = seconds = 0; + // Count the total number of uninfected cells and add each infected + // cell to the queue to represent level 0 of the level-order + // traversal. + for (int r = 0; r < matrix.length; r++) { + for (int c = 0; c < matrix[0].length; c++) { + if (matrix[r][c] == 1) { + ones += 1; + } else if (matrix[r][c] == 2) { + queue.offer(new int[]{r, c}); + } + } + } + // Use level-order traversal to determine how long it takes to + // infect the uninfected cells. + while (!queue.isEmpty() && ones > 0) { + // 1 second passes with each level of the matrix that's explored. + seconds++; + int size = queue.size(); + for (int i = 0; i < size; i++) { + int[] pos = queue.poll(); + int r = pos[0]; + int c = pos[1]; + // Infect any neighboring 1s and add them to the queue to be + // processed in the next level. + for (int[] d : dirs) { + int nextR = r + d[0]; + int nextC = c + d[1]; + if (isWithinBounds(nextR, nextC, matrix) && matrix[nextR][nextC] == 1) { + matrix[nextR][nextC] = 2; + ones--; + queue.offer(new int[]{nextR, nextC}); + } + } + } + } + // If there are still uninfected cells left, return -1. Otherwise, + // return the time passed. + return ones == 0 ? seconds : -1; + } + + private boolean isWithinBounds(int r, int c, int[][] matrix) { + return 0 <= r && r < matrix.length && 0 <= c && c < matrix[0].length; + } +} diff --git a/java/Graphs/MergingCommunities.java b/java/Graphs/MergingCommunities.java new file mode 100644 index 0000000..5e330aa --- /dev/null +++ b/java/Graphs/MergingCommunities.java @@ -0,0 +1,62 @@ +import java.util.Arrays; + +class UnionFind { + int[] parent; + int[] size; + + public UnionFind(int size) { + this.parent = new int[size]; + this.size = new int[size]; + for (int i = 0; i < size; i++) { + this.parent[i] = i; + } + Arrays.fill(this.size, 1); + } + + public void union(int x, int y) { + int repX = find(x); + int repY = find(y); + if (repX != repY) { + // If 'repX' represents a larger community, connect + // 'repY 's community to it. + if (this.size[repX] > this.size[repY]) { + this.parent[repY] = repX; + this.size[repX] += this.size[repY]; + } + // Otherwise, connect 'rep_x's community to 'rep_y'. + else { + this.parent[repX] = repY; + this.size[repY] += this.size[repX]; + } + } + } + + public int find(int x) { + if (x == this.parent[x]) { + return x; + } + // Path compression. + this.parent[x] = find(this.parent[x]); + return this.parent[x]; + } + + public int getSize(int x) { + return this.size[find(x)]; + } +} + +public class MergingCommunities { + UnionFind uf; + + public MergingCommunities(int n) { + this.uf = new UnionFind(n); + } + + public void connect(int x, int y) { + this.uf.union(x, y); + } + + public int getCommunitySize(int x) { + return this.uf.getSize(x); + } +} diff --git a/java/Graphs/Prerequisites.java b/java/Graphs/Prerequisites.java new file mode 100644 index 0000000..9126ad0 --- /dev/null +++ b/java/Graphs/Prerequisites.java @@ -0,0 +1,47 @@ +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +public class Prerequisites { + public boolean prerequisites(int n, int[][] prerequisites) { + Map> graph = new HashMap<>(); + int[] inDegrees = new int[n]; + // Represent the graph as an adjacency list and record the in- + // degree of each course. + for (int[] edge : prerequisites) { + int prerequisite = edge[0]; + int course = edge[1]; + graph.putIfAbsent(prerequisite, new ArrayList<>()); + graph.get(prerequisite).add(course); + inDegrees[course]++; + } + Queue queue = new LinkedList<>(); + // Add all courses with an in-degree of 0 to the queue. + for (int i = 0; i < n; i++) { + if (inDegrees[i] == 0) { + queue.offer(i); + } + } + int enrolledCourses = 0; + // Perform topological sort. + while (!queue.isEmpty()) { + int node = queue.poll(); + enrolledCourses++; + if (graph.containsKey(node)) { + for (int neighbor : graph.get(node)) { + inDegrees[neighbor]--; + // If the in-degree of a neighboring course becomes 0, add + // it to the queue. + if (inDegrees[neighbor] == 0) { + queue.offer(neighbor); + } + } + } + } + // Return true if we've successfully enrolled in all courses. + return enrolledCourses == n; + } +} diff --git a/java/Graphs/ShortestPath.java b/java/Graphs/ShortestPath.java new file mode 100644 index 0000000..12806ec --- /dev/null +++ b/java/Graphs/ShortestPath.java @@ -0,0 +1,59 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; + +public class ShortestPath { + public int[] shortestPath(int n, int[][] edges, int start) { + Map> graph = new HashMap<>(); + int[] distances = new int[n]; + Arrays.fill(distances, Integer.MAX_VALUE); + distances[start] = 0; + // Represent the graph as an adjacency list. + for (int[] edge : edges) { + int u = edge[0]; + int v = edge[1]; + int w = edge[2]; + + graph.putIfAbsent(u, new ArrayList<>()); + graph.putIfAbsent(v, new ArrayList<>()); + graph.get(u).add(new int[]{v, w}); + graph.get(v).add(new int[]{u, w}); + } + PriorityQueue minHeap = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0])); + minHeap.offer(new int[]{0, start}); // (distance, node) + // Use Dijkstra's algorithm to find the shortest path between the start node + // and all other nodes. + while (!minHeap.isEmpty()) { + int[] curr = minHeap.poll(); + int currDist = curr[0]; + int currNode = curr[1]; + // If the current distance to this node is greater than the recorded + // distance, we've already found the shortest distance to this node. + if (currDist > distances[currNode]) { + continue; + } + // Update the distances of the neighboring nodes. + for (int[] edge : graph.get(currNode)) { + int neighbor = edge[0]; + int weight = edge[1]; + int neighborDist = currDist + weight; + // Only update the distance if we find a shorter path to this + // neighbor. + if (neighborDist < distances[neighbor]) { + distances[neighbor] = neighborDist; + minHeap.offer(new int[]{neighborDist, neighbor}); + } + } + } + // Convert all infinity values to -1, representing unreachable nodes. + for (int i = 0; i < n; i++) { + if (distances[i] == Integer.MAX_VALUE) { + distances[i] = -1; + } + } + return distances; + } +} diff --git a/java/Graphs/ShortestTransformationSequence.java b/java/Graphs/ShortestTransformationSequence.java new file mode 100644 index 0000000..635e1e9 --- /dev/null +++ b/java/Graphs/ShortestTransformationSequence.java @@ -0,0 +1,57 @@ +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class ShortestTransformationSequence { + public int shortestTransformationSequence(String start, String end, List dictionary) { + Set dictionarySet = new HashSet<>(dictionary); + if (!dictionarySet.contains(start) && !dictionarySet.contains(end)) { + return 0; + } + if (start == end) { + return 1; + } + String lowerCaseAlphabet = "abcdefghijklmnopqrstuvwxyz"; + Queue queue = new LinkedList<>(); + Set visited = new HashSet<>(); + queue.offer(start); + visited.add(start); + int dist = 0; + // Use level-order traversal to find the shortest path from the + // start word to the end word. + while (!queue.isEmpty()) { + int size = queue.size(); + for (int j = 0; j < size; j++) { + String currWord = queue.poll(); + // If we found the end word, we've reached it via the + // shortest path. + if (currWord.equals(end)) { + return dist + 1; + } + // Generate all possible words that have a one-letter + // difference to the current word. + char[] currWordArray = currWord.toCharArray(); + for (int i = 0; i < currWord.length(); i++) { + char tmp = currWordArray[i]; + for (char c : lowerCaseAlphabet.toCharArray()) { + currWordArray[i] = c; + String nextWord = new String(currWordArray); + // If 'nextWord' exists in the dictionary, it's a + // neighbor of the current word. If unvisited, add it + // to the queue to be processed in the next level. + if (dictionarySet.contains(nextWord) && !visited.contains(nextWord)) { + visited.add(nextWord); + queue.offer(nextWord); + } + } + currWordArray[i] = tmp; + } + } + dist++; + } + // If there is no way to reach the end node, then no path exists. + return 0; + } +} diff --git a/java/Graphs/ShortestTransformationSequenceOptimized.java b/java/Graphs/ShortestTransformationSequenceOptimized.java new file mode 100644 index 0000000..b4d9c00 --- /dev/null +++ b/java/Graphs/ShortestTransformationSequenceOptimized.java @@ -0,0 +1,77 @@ +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class ShortestTransformationSequenceOptimized { + public int shortestTransformationSequenceOptimized(String start, String end, List dictionary) { + Set dictionarySet = new HashSet<>(dictionary); + if (!dictionarySet.contains(start) && !dictionarySet.contains(end)) { + return 0; + } + if (start == end) { + return 1; + } + Queue startQueue = new LinkedList<>(); + Set startVisited = new HashSet<>(); + Queue endQueue = new LinkedList<>(); + Set endVisited = new HashSet<>(); + startQueue.offer(start); + startVisited.add(start); + endQueue.offer(end); + endVisited.add(end); + int levelStart, levelEnd; + levelStart = levelEnd = 0; + // Perform a level-order traversal from the start word and another + // from the end word. + while (!startQueue.isEmpty() && !endQueue.isEmpty()) { + // Explore the next level of the traversal that starts from the + // start word. If it meets the other traversal, the shortest + // path between 'start' and 'end' has been found. + levelStart++; + if (exploreLevel(startQueue, startVisited, endVisited, dictionarySet)) { + return levelStart + levelEnd + 1; + } + // Explore the next level of the traversal that starts from the + // end word. + levelEnd++; + if (exploreLevel(endQueue, endVisited, startVisited, dictionarySet)) { + return levelStart + levelEnd + 1; + } + } + // If the traversals never met, then no path exists. + return 0; + } + + // This function explores the next level in the level-order traversal + // and checks if two searches meet. + private boolean exploreLevel(Queue queue, Set visited, Set otherVisited, Set dictionarySet) { + String lowerCaseAlphabet = "abcdefghijklmnopqrstuvwxyz"; + int size = queue.size(); + for (int j = 0; j < size; j++) { + String currWord = queue.poll(); + char[] currWordArray = currWord.toCharArray(); + for (int i = 0; i < currWord.length(); i++) { + char tmp = currWordArray[i]; + for (char c : lowerCaseAlphabet.toCharArray()) { + currWordArray[i] = c; + String nextWord = new String(currWordArray); + // If 'nextWord' has been visited during the other + // traversal, it means both searches have met. + if (otherVisited.contains(nextWord)) { + return true; + } + if (dictionarySet.contains(nextWord) && !visited.contains(nextWord)) { + visited.add(nextWord); + queue.offer(nextWord); + } + } + currWordArray[i] = tmp; + } + } + // If no word has been visited by the other traversal, the searches + // have not met yet. + return false; + } +}