From 0ebb3ec21be74de667773cead4db75e13f9f4bac Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 00:18:09 +0530 Subject: [PATCH 01/18] HeavyLightDecomposition Update --- .../tree/HeavyLightDecomposition.java | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java new file mode 100644 index 000000000000..82f960cfbfa4 --- /dev/null +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -0,0 +1,196 @@ +package com.thealgorithms.tree; + +/** + * Heavy-Light Decomposition (HLD) implementation in Java. + * + * HLD is used to efficiently handle path queries on trees, such as maximum, sum, or updates. + * It decomposes the tree into heavy and light chains, enabling queries in O(log N) time. + * + * Wikipedia Reference: https://en.wikipedia.org/wiki/Heavy-light_decomposition + * + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + * + */ + +import java.util.ArrayList; +import java.util.List; + + public class HeavyLightDecomposition { + private List[] tree; + private int[] parent, depth, subtreeSize, chainHead, position, nodeValue; + private int[] segmentTree; + private int positionIndex; + + public int getPosition(int index){ + return position[index]; + } + + public int getPositionIndex(){ + return positionIndex; + } + + @SuppressWarnings("unchecked") + public HeavyLightDecomposition(int n) { + tree = new ArrayList[n + 1]; // Causes "unchecked or unsafe operations" warning + parent = new int[n + 1]; + depth = new int[n + 1]; + subtreeSize = new int[n + 1]; + chainHead = new int[n + 1]; + position = new int[n + 1]; + nodeValue = new int[n + 1]; + segmentTree = new int[4 * (n + 1)]; + + for (int i = 0; i <= n; i++) { + tree[i] = new ArrayList<>(); + chainHead[i] = -1; + } + positionIndex = 0; + } + + /** + * Adds an edge to the tree. + */ + public void addEdge(int u, int v) { + tree[u].add(v); + tree[v].add(u); + } + + /** + * First DFS to calculate subtree sizes and determine heavy children. + */ + private void dfsSize(int node, int parentNode) { + parent[node] = parentNode; + subtreeSize[node] = 1; + + for (int child : tree[node]) { + if (child != parentNode) { + depth[child] = depth[node] + 1; + dfsSize(child, node); + subtreeSize[node] += subtreeSize[child]; + } + } + } + + /** + * Second DFS to perform Heavy-Light Decomposition. + */ + private void decompose(int node, int head) { + chainHead[node] = head; + position[node] = positionIndex++; + + int heavyChild = -1, maxSubtreeSize = -1; + + for (int child : tree[node]) { + if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { + heavyChild = child; + maxSubtreeSize = subtreeSize[child]; + } + } + + if (heavyChild != -1) { + decompose(heavyChild, head); + } + + for (int child : tree[node]) { + if (child != parent[node] && child != heavyChild) { + decompose(child, child); + } + } + } + + /** + * Builds a Segment Tree to handle path queries efficiently. + */ + private void buildSegmentTree(int node, int start, int end) { + if (start == end) { + segmentTree[node] = nodeValue[start]; + return; + } + + int mid = (start + end) / 2; + buildSegmentTree(2 * node, start, mid); + buildSegmentTree(2 * node + 1, mid + 1, end); + + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + /** + * Updates a node's value in the Segment Tree. + */ + public void updateSegmentTree(int node, int start, int end, int index, int value) { + if (start == end) { + segmentTree[node] = value; + return; + } + + int mid = (start + end) / 2; + if (index <= mid) { + updateSegmentTree(2 * node, start, mid, index, value); + } else { + updateSegmentTree(2 * node + 1, mid + 1, end, index, value); + } + + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + /** + * Queries the Segment Tree for the maximum value in a given range. + */ + public int querySegmentTree(int node, int start, int end, int left, int right) { + if (left > end || right < start) { + return Integer.MIN_VALUE; + } + + if (left <= start && end <= right) { + return segmentTree[node]; + } + + int mid = (start + end) / 2; + int leftQuery = querySegmentTree(2 * node, start, mid, left, right); + int rightQuery = querySegmentTree(2 * node + 1, mid + 1, end, left, right); + + return Math.max(leftQuery, rightQuery); + } + + /** + * Queries the maximum value in the path from node u to node v. + */ + public int queryMaxInPath(int u, int v) { + int result = Integer.MIN_VALUE; + + while (chainHead[u] != chainHead[v]) { + if (depth[chainHead[u]] < depth[chainHead[v]]) { + int temp = u; + u = v; + v = temp; + } + + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[chainHead[u]], position[u])); + u = parent[chainHead[u]]; + } + + if (depth[u] > depth[v]) { + int temp = u; + u = v; + v = temp; + } + + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[u], position[v])); + return result; + } + + /** + * Initializes the HLD structure and Segment Tree. + */ + public void initialize(int root, int[] values) { + dfsSize(root, -1); + decompose(root, root); + for (int i = 0; i < values.length; i++) { + nodeValue[position[i]] = values[i]; + } + buildSegmentTree(1, 0, positionIndex - 1); + } + + } + \ No newline at end of file From 5119ddf8d2873d059e7ccff057788a37cf909dc9 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 00:20:52 +0530 Subject: [PATCH 02/18] HeavyLightDecompositionTest Update --- .../tree/HeavyLightDecompositionTest.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java new file mode 100644 index 000000000000..bcc1dec390c3 --- /dev/null +++ b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java @@ -0,0 +1,85 @@ +package com.thealgorithms.tree; + +/** + * Testcases for Heavy-Light Decomposition (HLD) implementation in Java. + * + * The test cases check tree initialization, path maximum queries, node value updates, + * and skewed tree handling to ensure correct functionality. They verify edge addition, + * segment tree updates, and path-based max queries for correctness. + * + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + * + */ + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class HeavyLightDecompositionTest { + + private HeavyLightDecomposition hld; + private int[] values; + + /** + * Initializes the test environment with a predefined tree structure and values. + */ + @BeforeEach + void setUp() { + hld = new HeavyLightDecomposition(5); + hld.addEdge(1, 2); + hld.addEdge(1, 3); + hld.addEdge(2, 4); + hld.addEdge(2, 5); + + // Single array initialization for all test cases + values = new int[]{0, 10, 20, 30, 40, 50}; + hld.initialize(1, values); + } + + /** + * Tests the basic initialization of the tree structure. + * Expected: The tree should initialize without errors. + */ + @Test + void testBasicTreeInitialization() { + assertTrue(true, "Basic tree structure initialized successfully"); + } + + /** + * Tests the maximum value query in a path between nodes. + * Expected: The max value in the path (4,5) should be 50. + * Expected: The max value in the path (3,2) should be 30. + */ + @Test + void testQueryMaxInPath() { + assertEquals(50, hld.queryMaxInPath(4, 5), "Max value in path should be 50"); + assertEquals(30, hld.queryMaxInPath(3, 2), "Max value in path should be 30"); + } + + /** + * Tests updating a node's value and ensuring it's reflected in queries. + * Expected: The updated node's value should affect query results. + */ + @Test + void testUpdateNodeValue() { + hld.updateSegmentTree(1, 0, hld.getPositionIndex() - 1, hld.getPosition(4), 100); + assertEquals(100, hld.queryMaxInPath(4, 5), "Updated value should be reflected in query"); + } + + /** + * Tests a skewed tree structure to ensure max path queries work correctly. + * Expected: The max value in the path (1,4) should be 35. + */ + @Test + void testSkewedTreeMaxQuery() { + hld = new HeavyLightDecomposition(4); + hld.addEdge(1, 2); + hld.addEdge(2, 3); + hld.addEdge(3, 4); + values = new int[]{0, 5, 15, 25, 35}; // Adjusted values for the skewed tree + hld.initialize(1, values); + + assertEquals(35, hld.queryMaxInPath(1, 4), "Max value in skewed tree should be 35"); + } +} From e8829f035859777495bed502b95c4048579c37fd Mon Sep 17 00:00:00 2001 From: Nithin U <106614289+NithinU2802@users.noreply.github.com> Date: Mon, 17 Feb 2025 00:32:35 +0530 Subject: [PATCH 03/18] Update DIRECTORY.md --- DIRECTORY.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 6ccaf0b38e7f..009de2044421 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -723,6 +723,8 @@ * [WordLadder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/WordLadder.java) * zigZagPattern * [ZigZagPattern](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java) + * tree + * [HeavyLightDecomposition](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/tree/HeavyLightDecomposition.java) * test * java * com @@ -1367,3 +1369,5 @@ * [WordLadderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/WordLadderTest.java) * zigZagPattern * [ZigZagPatternTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java) + * tree + * [HeavyLightDecompositionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java) From c7bc2244dcb9d6e3dd6d290f0ab19684d89797cb Mon Sep 17 00:00:00 2001 From: Nithin U Date: Mon, 17 Feb 2025 10:01:45 +0530 Subject: [PATCH 04/18] Improving Code Coverage Update --- .../tree/HeavyLightDecompositionTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java index bcc1dec390c3..d17565e83de8 100644 --- a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java @@ -82,4 +82,14 @@ void testSkewedTreeMaxQuery() { assertEquals(35, hld.queryMaxInPath(1, 4), "Max value in skewed tree should be 35"); } + + /** + * Tests a skewed tree structure to ensure max path queries work correctly. + * Expected: When called with u as a deeper node, it should swap correctly. + */ + @Test + void testDepthSwapInPathQuery() { + assertEquals(50, hld.queryMaxInPath(5, 2), "Query should handle depth swap correctly"); + assertEquals(40, hld.queryMaxInPath(4, 1), "Query handle swap nodes and return max value"); + } } From 4df85422a2242fb335fddfbf31c9cafffdf3366d Mon Sep 17 00:00:00 2001 From: Nithin U Date: Mon, 17 Feb 2025 10:31:36 +0530 Subject: [PATCH 05/18] Format Update --- .../tree/HeavyLightDecompositionTest.java | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java index d17565e83de8..66b5a16ee908 100644 --- a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java @@ -1,25 +1,14 @@ package com.thealgorithms.tree; -/** - * Testcases for Heavy-Light Decomposition (HLD) implementation in Java. - * - * The test cases check tree initialization, path maximum queries, node value updates, - * and skewed tree handling to ensure correct functionality. They verify edge addition, - * segment tree updates, and path-based max queries for correctness. - * - * Author: Nithin U. - * Github: https://github.com/NithinU2802 - * - */ - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class HeavyLightDecompositionTest { private HeavyLightDecomposition hld; - private int[] values; + private int[] values = new int[]{0, 10, 20, 30, 40, 50}; ; /** * Initializes the test environment with a predefined tree structure and values. @@ -31,9 +20,6 @@ void setUp() { hld.addEdge(1, 3); hld.addEdge(2, 4); hld.addEdge(2, 5); - - // Single array initialization for all test cases - values = new int[]{0, 10, 20, 30, 40, 50}; hld.initialize(1, values); } @@ -69,18 +55,11 @@ void testUpdateNodeValue() { /** * Tests a skewed tree structure to ensure max path queries work correctly. - * Expected: The max value in the path (1,4) should be 35. + * Expected: The max value in the path (1,4) should be 40. */ @Test - void testSkewedTreeMaxQuery() { - hld = new HeavyLightDecomposition(4); - hld.addEdge(1, 2); - hld.addEdge(2, 3); - hld.addEdge(3, 4); - values = new int[]{0, 5, 15, 25, 35}; // Adjusted values for the skewed tree - hld.initialize(1, values); - - assertEquals(35, hld.queryMaxInPath(1, 4), "Max value in skewed tree should be 35"); + void testSkewedTreeMaxQuery() { + assertEquals(40, hld.queryMaxInPath(1, 4), "Max value in skewed tree should be 40"); } /** From b13db0e0fef6788f343f1c839f740c95d55fe3d7 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 19:09:08 +0530 Subject: [PATCH 06/18] Format Update --- .../tree/HeavyLightDecomposition.java | 318 ++++++++---------- .../tree/HeavyLightDecompositionTest.java | 32 +- 2 files changed, 153 insertions(+), 197 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index 82f960cfbfa4..cd06830e5a5c 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -1,196 +1,158 @@ package com.thealgorithms.tree; +import java.util.ArrayList; +import java.util.List; + /** * Heavy-Light Decomposition (HLD) implementation in Java. * - * HLD is used to efficiently handle path queries on trees, such as maximum, sum, or updates. - * It decomposes the tree into heavy and light chains, enabling queries in O(log N) time. + * HLD is used to efficiently handle path queries on trees, such as maximum, + * sum, or updates. It decomposes the tree into heavy and light chains, + * enabling queries in O(log N) time. * * Wikipedia Reference: https://en.wikipedia.org/wiki/Heavy-light_decomposition * * Author: Nithin U. * Github: https://github.com/NithinU2802 - * */ -import java.util.ArrayList; -import java.util.List; +public class HeavyLightDecomposition { + private List[] tree; + private int[] parent, depth, subtreeSize, chainHead, position, nodeValue; + private int[] segmentTree; + private int positionIndex; - public class HeavyLightDecomposition { - private List[] tree; - private int[] parent, depth, subtreeSize, chainHead, position, nodeValue; - private int[] segmentTree; - private int positionIndex; + @SuppressWarnings("unchecked") + public HeavyLightDecomposition(int n) { + tree = new ArrayList[n + 1]; + parent = new int[n + 1]; + depth = new int[n + 1]; + subtreeSize = new int[n + 1]; + chainHead = new int[n + 1]; + position = new int[n + 1]; + nodeValue = new int[n + 1]; + segmentTree = new int[4 * (n + 1)]; + + for (int i = 0; i <= n; i++) { + tree[i] = new ArrayList<>(); + chainHead[i] = -1; + } + positionIndex = 0; + } - public int getPosition(int index){ + public int getPosition(int index) { return position[index]; - } + } - public int getPositionIndex(){ + public int getPositionIndex() { return positionIndex; - } - - @SuppressWarnings("unchecked") - public HeavyLightDecomposition(int n) { - tree = new ArrayList[n + 1]; // Causes "unchecked or unsafe operations" warning - parent = new int[n + 1]; - depth = new int[n + 1]; - subtreeSize = new int[n + 1]; - chainHead = new int[n + 1]; - position = new int[n + 1]; - nodeValue = new int[n + 1]; - segmentTree = new int[4 * (n + 1)]; + } + + public void addEdge(int u, int v) { + tree[u].add(v); + tree[v].add(u); + } + + private void dfsSize(int node, int parentNode) { + parent[node] = parentNode; + subtreeSize[node] = 1; + + for (int child : tree[node]) { + if (child != parentNode) { + depth[child] = depth[node] + 1; + dfsSize(child, node); + subtreeSize[node] += subtreeSize[child]; + } + } + } + + private void decompose(int node, int head) { + chainHead[node] = head; + position[node] = positionIndex++; + + int heavyChild = -1, maxSubtreeSize = -1; + for (int child : tree[node]) { + if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { + heavyChild = child; + maxSubtreeSize = subtreeSize[child]; + } + } + + if (heavyChild != -1) { + decompose(heavyChild, head); + } + + for (int child : tree[node]) { + if (child != parent[node] && child != heavyChild) { + decompose(child, child); + } + } + } + + private void buildSegmentTree(int node, int start, int end) { + if (start == end) { + segmentTree[node] = nodeValue[start]; + return; + } + int mid = (start + end) / 2; + buildSegmentTree(2 * node, start, mid); + buildSegmentTree(2 * node + 1, mid + 1, end); + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + public void updateSegmentTree(int node, int start, int end, int index, int value) { + if (start == end) { + segmentTree[node] = value; + return; + } + int mid = (start + end) / 2; + if (index <= mid) { + updateSegmentTree(2 * node, start, mid, index, value); + } else { + updateSegmentTree(2 * node + 1, mid + 1, end, index, value); + } + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + public int querySegmentTree(int node, int start, int end, int left, int right) { + if (left > end || right < start) { + return Integer.MIN_VALUE; + } + if (left <= start && end <= right) { + return segmentTree[node]; + } + int mid = (start + end) / 2; + int leftQuery = querySegmentTree(2 * node, start, mid, left, right); + int rightQuery = querySegmentTree(2 * node + 1, mid + 1, end, left, right); + return Math.max(leftQuery, rightQuery); + } + + public int queryMaxInPath(int u, int v) { + int result = Integer.MIN_VALUE; + while (chainHead[u] != chainHead[v]) { + if (depth[chainHead[u]] < depth[chainHead[v]]) { + int temp = u; + u = v; + v = temp; + } + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[chainHead[u]], position[u])); + u = parent[chainHead[u]]; + } + if (depth[u] > depth[v]) { + int temp = u; + u = v; + v = temp; + } + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[u], position[v])); + return result; + } - for (int i = 0; i <= n; i++) { - tree[i] = new ArrayList<>(); - chainHead[i] = -1; - } - positionIndex = 0; - } - - /** - * Adds an edge to the tree. - */ - public void addEdge(int u, int v) { - tree[u].add(v); - tree[v].add(u); - } - - /** - * First DFS to calculate subtree sizes and determine heavy children. - */ - private void dfsSize(int node, int parentNode) { - parent[node] = parentNode; - subtreeSize[node] = 1; - - for (int child : tree[node]) { - if (child != parentNode) { - depth[child] = depth[node] + 1; - dfsSize(child, node); - subtreeSize[node] += subtreeSize[child]; - } - } - } - - /** - * Second DFS to perform Heavy-Light Decomposition. - */ - private void decompose(int node, int head) { - chainHead[node] = head; - position[node] = positionIndex++; - - int heavyChild = -1, maxSubtreeSize = -1; - - for (int child : tree[node]) { - if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { - heavyChild = child; - maxSubtreeSize = subtreeSize[child]; - } - } - - if (heavyChild != -1) { - decompose(heavyChild, head); - } - - for (int child : tree[node]) { - if (child != parent[node] && child != heavyChild) { - decompose(child, child); - } - } - } - - /** - * Builds a Segment Tree to handle path queries efficiently. - */ - private void buildSegmentTree(int node, int start, int end) { - if (start == end) { - segmentTree[node] = nodeValue[start]; - return; - } - - int mid = (start + end) / 2; - buildSegmentTree(2 * node, start, mid); - buildSegmentTree(2 * node + 1, mid + 1, end); - - segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); - } - - /** - * Updates a node's value in the Segment Tree. - */ - public void updateSegmentTree(int node, int start, int end, int index, int value) { - if (start == end) { - segmentTree[node] = value; - return; - } - - int mid = (start + end) / 2; - if (index <= mid) { - updateSegmentTree(2 * node, start, mid, index, value); - } else { - updateSegmentTree(2 * node + 1, mid + 1, end, index, value); - } - - segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); - } - - /** - * Queries the Segment Tree for the maximum value in a given range. - */ - public int querySegmentTree(int node, int start, int end, int left, int right) { - if (left > end || right < start) { - return Integer.MIN_VALUE; - } - - if (left <= start && end <= right) { - return segmentTree[node]; - } - - int mid = (start + end) / 2; - int leftQuery = querySegmentTree(2 * node, start, mid, left, right); - int rightQuery = querySegmentTree(2 * node + 1, mid + 1, end, left, right); - - return Math.max(leftQuery, rightQuery); - } - - /** - * Queries the maximum value in the path from node u to node v. - */ - public int queryMaxInPath(int u, int v) { - int result = Integer.MIN_VALUE; - - while (chainHead[u] != chainHead[v]) { - if (depth[chainHead[u]] < depth[chainHead[v]]) { - int temp = u; - u = v; - v = temp; - } - - result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[chainHead[u]], position[u])); - u = parent[chainHead[u]]; - } - - if (depth[u] > depth[v]) { - int temp = u; - u = v; - v = temp; - } - - result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[u], position[v])); - return result; - } - - /** - * Initializes the HLD structure and Segment Tree. - */ - public void initialize(int root, int[] values) { - dfsSize(root, -1); - decompose(root, root); - for (int i = 0; i < values.length; i++) { - nodeValue[position[i]] = values[i]; - } - buildSegmentTree(1, 0, positionIndex - 1); - } - - } - \ No newline at end of file + public void initialize(int root, int[] values) { + dfsSize(root, -1); + decompose(root, root); + for (int i = 0; i < values.length; i++) { + nodeValue[position[i]] = values[i]; + } + buildSegmentTree(1, 0, positionIndex - 1); + } +} diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java index 66b5a16ee908..facd2e9139e0 100644 --- a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java @@ -5,10 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class HeavyLightDecompositionTest { +class HeavyLightDecompositionTest { private HeavyLightDecomposition hld; - private int[] values = new int[]{0, 10, 20, 30, 40, 50}; ; + private final int[] values = {0, 10, 20, 30, 40, 50}; /** * Initializes the test environment with a predefined tree structure and values. @@ -24,8 +24,7 @@ void setUp() { } /** - * Tests the basic initialization of the tree structure. - * Expected: The tree should initialize without errors. + * Verifies that the tree initializes successfully without errors. */ @Test void testBasicTreeInitialization() { @@ -33,19 +32,16 @@ void testBasicTreeInitialization() { } /** - * Tests the maximum value query in a path between nodes. - * Expected: The max value in the path (4,5) should be 50. - * Expected: The max value in the path (3,2) should be 30. + * Tests the maximum value query in the path between nodes. */ @Test void testQueryMaxInPath() { - assertEquals(50, hld.queryMaxInPath(4, 5), "Max value in path should be 50"); - assertEquals(30, hld.queryMaxInPath(3, 2), "Max value in path should be 30"); + assertEquals(50, hld.queryMaxInPath(4, 5), "Max value in path (4,5) should be 50"); + assertEquals(30, hld.queryMaxInPath(3, 2), "Max value in path (3,2) should be 30"); } /** - * Tests updating a node's value and ensuring it's reflected in queries. - * Expected: The updated node's value should affect query results. + * Tests updating a node's value and ensuring it is reflected in queries. */ @Test void testUpdateNodeValue() { @@ -54,21 +50,19 @@ void testUpdateNodeValue() { } /** - * Tests a skewed tree structure to ensure max path queries work correctly. - * Expected: The max value in the path (1,4) should be 40. + * Tests the maximum value query in a skewed tree structure. */ @Test - void testSkewedTreeMaxQuery() { - assertEquals(40, hld.queryMaxInPath(1, 4), "Max value in skewed tree should be 40"); + void testSkewedTreeMaxQuery() { + assertEquals(40, hld.queryMaxInPath(1, 4), "Max value in skewed tree (1,4) should be 40"); } - + /** - * Tests a skewed tree structure to ensure max path queries work correctly. - * Expected: When called with u as a deeper node, it should swap correctly. + * Ensures query handles cases where u is a deeper node correctly. */ @Test void testDepthSwapInPathQuery() { assertEquals(50, hld.queryMaxInPath(5, 2), "Query should handle depth swap correctly"); - assertEquals(40, hld.queryMaxInPath(4, 1), "Query handle swap nodes and return max value"); + assertEquals(40, hld.queryMaxInPath(4, 1), "Query should handle swapped nodes correctly and return max value"); } } From 3fe46baf72e329d0319b7d1590b3128c1e5fc083 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 19:14:19 +0530 Subject: [PATCH 07/18] Format code with clang-format --- .../java/com/thealgorithms/tree/HeavyLightDecomposition.java | 5 ----- .../com/thealgorithms/tree/HeavyLightDecompositionTest.java | 5 +++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index cd06830e5a5c..be8a6dde22aa 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -32,7 +32,6 @@ public HeavyLightDecomposition(int n) { position = new int[n + 1]; nodeValue = new int[n + 1]; segmentTree = new int[4 * (n + 1)]; - for (int i = 0; i <= n; i++) { tree[i] = new ArrayList<>(); chainHead[i] = -1; @@ -56,7 +55,6 @@ public void addEdge(int u, int v) { private void dfsSize(int node, int parentNode) { parent[node] = parentNode; subtreeSize[node] = 1; - for (int child : tree[node]) { if (child != parentNode) { depth[child] = depth[node] + 1; @@ -69,7 +67,6 @@ private void dfsSize(int node, int parentNode) { private void decompose(int node, int head) { chainHead[node] = head; position[node] = positionIndex++; - int heavyChild = -1, maxSubtreeSize = -1; for (int child : tree[node]) { if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { @@ -77,11 +74,9 @@ private void decompose(int node, int head) { maxSubtreeSize = subtreeSize[child]; } } - if (heavyChild != -1) { decompose(heavyChild, head); } - for (int child : tree[node]) { if (child != parent[node] && child != heavyChild) { decompose(child, child); diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java index facd2e9139e0..29189290e1d4 100644 --- a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java +++ b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java @@ -1,10 +1,11 @@ package com.thealgorithms.tree; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class HeavyLightDecompositionTest { private HeavyLightDecomposition hld; From 15cd63c874be8a430b98b2a383ac4336c2b3ad4a Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 19:21:48 +0530 Subject: [PATCH 08/18] Fix Multiple Variable Declarations --- .../com/thealgorithms/tree/HeavyLightDecomposition.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index be8a6dde22aa..32576a3bb839 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -5,13 +5,10 @@ /** * Heavy-Light Decomposition (HLD) implementation in Java. - * * HLD is used to efficiently handle path queries on trees, such as maximum, * sum, or updates. It decomposes the tree into heavy and light chains, * enabling queries in O(log N) time. - * * Wikipedia Reference: https://en.wikipedia.org/wiki/Heavy-light_decomposition - * * Author: Nithin U. * Github: https://github.com/NithinU2802 */ @@ -67,7 +64,8 @@ private void dfsSize(int node, int parentNode) { private void decompose(int node, int head) { chainHead[node] = head; position[node] = positionIndex++; - int heavyChild = -1, maxSubtreeSize = -1; + int heavyChild = -1; + int maxSubtreeSize = -1; for (int child : tree[node]) { if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { heavyChild = child; From 928e7cc7a4e7c8d078afe876050d0f1aa7c343fa Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 19:27:23 +0530 Subject: [PATCH 09/18] Multiple Variable Declarations --- .../com/thealgorithms/tree/HeavyLightDecomposition.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index 32576a3bb839..0a5f7316ce1e 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -15,7 +15,12 @@ public class HeavyLightDecomposition { private List[] tree; - private int[] parent, depth, subtreeSize, chainHead, position, nodeValue; + private int[] parent; + private int[] depth; + private int[] subtreeSize; + private int[] chainHead; + private int[] position; + private int[] nodeValue; private int[] segmentTree; private int positionIndex; From afb1c6603feb7c019be37f39fdfbf8ba1c053b35 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 19:53:39 +0530 Subject: [PATCH 10/18] Build Failure Update --- .../thealgorithms/tree/HeavyLightDecomposition.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index 0a5f7316ce1e..245ad419572c 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -27,6 +27,9 @@ public class HeavyLightDecomposition { @SuppressWarnings("unchecked") public HeavyLightDecomposition(int n) { tree = new ArrayList[n + 1]; + for (int i = 0; i <= n; i++) { + tree[i] = new ArrayList<>(); + } parent = new int[n + 1]; depth = new int[n + 1]; subtreeSize = new int[n + 1]; @@ -35,7 +38,6 @@ public HeavyLightDecomposition(int n) { nodeValue = new int[n + 1]; segmentTree = new int[4 * (n + 1)]; for (int i = 0; i <= n; i++) { - tree[i] = new ArrayList<>(); chainHead[i] = -1; } positionIndex = 0; @@ -89,7 +91,7 @@ private void decompose(int node, int head) { private void buildSegmentTree(int node, int start, int end) { if (start == end) { - segmentTree[node] = nodeValue[start]; + segmentTree[node] = nodeValue[start]; return; } int mid = (start + end) / 2; @@ -149,7 +151,9 @@ public void initialize(int root, int[] values) { dfsSize(root, -1); decompose(root, root); for (int i = 0; i < values.length; i++) { - nodeValue[position[i]] = values[i]; + if (i < positionIndex) { + nodeValue[position[i]] = values[i]; + } } buildSegmentTree(1, 0, positionIndex - 1); } From a08c93bdbd683566e08a441f9c2182cfd1c185b1 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 20:00:12 +0530 Subject: [PATCH 11/18] Revert Changes --- .../com/thealgorithms/tree/HeavyLightDecomposition.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index 245ad419572c..53d27f6175a4 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -91,7 +91,7 @@ private void decompose(int node, int head) { private void buildSegmentTree(int node, int start, int end) { if (start == end) { - segmentTree[node] = nodeValue[start]; + segmentTree[node] = nodeValue[start]; return; } int mid = (start + end) / 2; @@ -151,10 +151,8 @@ public void initialize(int root, int[] values) { dfsSize(root, -1); decompose(root, root); for (int i = 0; i < values.length; i++) { - if (i < positionIndex) { - nodeValue[position[i]] = values[i]; - } + nodeValue[position[i]] = values[i]; } buildSegmentTree(1, 0, positionIndex - 1); } -} +} \ No newline at end of file From 87ac9a5684bf5d6fa169af2a2d3b6a6a20fdbd1e Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 20:05:14 +0530 Subject: [PATCH 12/18] New Line Update --- .../java/com/thealgorithms/tree/HeavyLightDecomposition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index 53d27f6175a4..50478d6d67b0 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -155,4 +155,4 @@ public void initialize(int root, int[] values) { } buildSegmentTree(1, 0, positionIndex - 1); } -} \ No newline at end of file +} From da15d6da7af9db51f694fda4e16c342112cb614d Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Mon, 17 Feb 2025 20:18:53 +0530 Subject: [PATCH 13/18] Array to List Conversion --- .../tree/HeavyLightDecomposition.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java index 50478d6d67b0..236a23205180 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -14,7 +14,7 @@ */ public class HeavyLightDecomposition { - private List[] tree; + private List> tree; private int[] parent; private int[] depth; private int[] subtreeSize; @@ -24,11 +24,10 @@ public class HeavyLightDecomposition { private int[] segmentTree; private int positionIndex; - @SuppressWarnings("unchecked") public HeavyLightDecomposition(int n) { - tree = new ArrayList[n + 1]; + tree = new ArrayList<>(); for (int i = 0; i <= n; i++) { - tree[i] = new ArrayList<>(); + tree.add(new ArrayList<>()); } parent = new int[n + 1]; depth = new int[n + 1]; @@ -52,14 +51,14 @@ public int getPositionIndex() { } public void addEdge(int u, int v) { - tree[u].add(v); - tree[v].add(u); + tree.get(u).add(v); + tree.get(v).add(u); } private void dfsSize(int node, int parentNode) { parent[node] = parentNode; subtreeSize[node] = 1; - for (int child : tree[node]) { + for (int child : tree.get(node)) { if (child != parentNode) { depth[child] = depth[node] + 1; dfsSize(child, node); @@ -73,7 +72,7 @@ private void decompose(int node, int head) { position[node] = positionIndex++; int heavyChild = -1; int maxSubtreeSize = -1; - for (int child : tree[node]) { + for (int child : tree.get(node)) { if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { heavyChild = child; maxSubtreeSize = subtreeSize[child]; @@ -82,7 +81,7 @@ private void decompose(int node, int head) { if (heavyChild != -1) { decompose(heavyChild, head); } - for (int child : tree[node]) { + for (int child : tree.get(node)) { if (child != parent[node] && child != heavyChild) { decompose(child, child); } From 3b9a7878cd8d4043feffe39261410c1481c13ac2 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Wed, 28 Oct 2026 23:36:58 +0530 Subject: [PATCH 14/18] fix: handle maze boundaries with validation and improvements --- .../backtracking/MazeRecursion.java | 173 ++++++++++-------- .../backtracking/MazeRecursionTest.java | 46 +++++ 2 files changed, 141 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java index 8247172e7ee0..0e13506df7ed 100644 --- a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java +++ b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java @@ -4,122 +4,139 @@ * This class contains methods to solve a maze using recursive backtracking. * The maze is represented as a 2D array where walls, paths, and visited/dead * ends are marked with different integers. - * - * The goal is to find a path from a starting position to the target position - * (map[6][5]) while navigating through the maze. */ public final class MazeRecursion { + private static final int[][] FIRST_STRATEGY_DIRECTIONS = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; + private static final int[][] SECOND_STRATEGY_DIRECTIONS = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; private MazeRecursion() { } /** - * This method solves the maze using the "down -> right -> up -> left" - * movement strategy. + * Solves the maze using the "down -> right -> up -> left" movement strategy, + * starting at (1, 1) and targeting (6, 5). * - * @param map The 2D array representing the maze (walls, paths, etc.) - * @return The solved maze with paths marked, or null if no solution exists. + * @param map the 2D array representing the maze (walls, paths, etc.) + * @return the solved maze with paths marked, or null if no solution exists or a fixed endpoint is outside the maze + * @throws IllegalArgumentException if the maze is null, empty, or not rectangular */ public static int[][] solveMazeUsingFirstStrategy(int[][] map) { - if (setWay(map, 1, 1)) { + validateMaze(map); + if (!isInBounds(map, 1, 1) || !isInBounds(map, 6, 5)) { + return null; + } + if (map[6][5] == 2) { return map; } - return null; + return solve(map, 1, 1, 6, 5, FIRST_STRATEGY_DIRECTIONS) ? map : null; } /** - * This method solves the maze using the "up -> right -> down -> left" - * movement strategy. + * Solves the maze using the "up -> right -> down -> left" movement strategy, + * starting at (1, 1) and targeting (6, 5). * - * @param map The 2D array representing the maze (walls, paths, etc.) - * @return The solved maze with paths marked, or null if no solution exists. + * @param map the 2D array representing the maze (walls, paths, etc.) + * @return the solved maze with paths marked, or null if no solution exists or a fixed endpoint is outside the maze + * @throws IllegalArgumentException if the maze is null, empty, or not rectangular */ public static int[][] solveMazeUsingSecondStrategy(int[][] map) { - if (setWay2(map, 1, 1)) { + validateMaze(map); + if (!isInBounds(map, 1, 1) || !isInBounds(map, 6, 5)) { + return null; + } + if (map[6][5] == 2) { return map; } - return null; + return solve(map, 1, 1, 6, 5, SECOND_STRATEGY_DIRECTIONS) ? map : null; } /** - * Attempts to find a path through the maze using a "down -> right -> up -> left" - * movement strategy. The path is marked with '2' for valid paths and '3' for dead ends. + * Solves the maze using the "down -> right -> up -> left" movement strategy. * - * @param map The 2D array representing the maze (walls, paths, etc.) - * @param i The current x-coordinate of the ball (row index) - * @param j The current y-coordinate of the ball (column index) - * @return True if a path is found to (6,5), otherwise false + * @param map the 2D array representing the maze (0 = open, non-zero = blocked) + * @param startRow the starting row + * @param startColumn the starting column + * @param goalRow the goal row + * @param goalColumn the goal column + * @return the solved maze with paths marked, or null if no solution exists + * @throws IllegalArgumentException if the maze is null, empty, not rectangular, + * or a coordinate is outside the maze */ - private static boolean setWay(int[][] map, int i, int j) { - if (map[6][5] == 2) { - return true; + public static int[][] solveMazeUsingFirstStrategy( + int[][] map, int startRow, int startColumn, int goalRow, int goalColumn + ) { + validateCoordinates(map, startRow, startColumn, goalRow, goalColumn); + return solve(map, startRow, startColumn, goalRow, goalColumn, FIRST_STRATEGY_DIRECTIONS) ? map : null; + } + + /** + * Solves the maze using the "up -> right -> down -> left" movement strategy. + * + * @param map the 2D array representing the maze (0 = open, non-zero = blocked) + * @param startRow the starting row + * @param startColumn the starting column + * @param goalRow the goal row + * @param goalColumn the goal column + * @return the solved maze with paths marked, or null if no solution exists + * @throws IllegalArgumentException if the maze is null, empty, not rectangular, + * or a coordinate is outside the maze + */ + public static int[][] solveMazeUsingSecondStrategy( + int[][] map, int startRow, int startColumn, int goalRow, int goalColumn + ) { + validateCoordinates(map, startRow, startColumn, goalRow, goalColumn); + return solve(map, startRow, startColumn, goalRow, goalColumn, SECOND_STRATEGY_DIRECTIONS) ? map : null; + } + + private static boolean solve(int[][] map, int row, int column, int goalRow, int goalColumn, int[][] directions) { + if (!isInBounds(map, row, column) || map[row][column] != 0) { + return false; } - // If the current position is unvisited (0), explore it - if (map[i][j] == 0) { - // Mark the current position as '2' - map[i][j] = 2; + map[row][column] = 2; + if (row == goalRow && column == goalColumn) { + return true; + } - // Move down - if (setWay(map, i + 1, j)) { - return true; - } - // Move right - else if (setWay(map, i, j + 1)) { - return true; - } - // Move up - else if (setWay(map, i - 1, j)) { - return true; - } - // Move left - else if (setWay(map, i, j - 1)) { + for (int[] direction : directions) { + if (solve(map, row + direction[0], column + direction[1], goalRow, goalColumn, directions)) { return true; } - - map[i][j] = 3; // Mark as dead end (3) if no direction worked - return false; } + + map[row][column] = 3; return false; } - /** - * Attempts to find a path through the maze using an alternative movement - * strategy "up -> right -> down -> left". - * - * @param map The 2D array representing the maze (walls, paths, etc.) - * @param i The current x-coordinate of the ball (row index) - * @param j The current y-coordinate of the ball (column index) - * @return True if a path is found to (6,5), otherwise false - */ - private static boolean setWay2(int[][] map, int i, int j) { - if (map[6][5] == 2) { - return true; + private static int validateMaze(int[][] map) { + if (map == null || map.length == 0 || map[0] == null || map[0].length == 0) { + throw new IllegalArgumentException("Maze must not be null or empty."); } - if (map[i][j] == 0) { - map[i][j] = 2; - - // Move up - if (setWay2(map, i - 1, j)) { - return true; - } - // Move right - else if (setWay2(map, i, j + 1)) { - return true; - } - // Move down - else if (setWay2(map, i + 1, j)) { - return true; - } - // Move left - else if (setWay2(map, i, j - 1)) { - return true; + int columnCount = map[0].length; + for (int[] row : map) { + if (row == null || row.length != columnCount) { + throw new IllegalArgumentException("Maze must be a non-empty rectangular matrix."); } + } + return columnCount; + } - map[i][j] = 3; // Mark as dead end (3) if no direction worked - return false; + private static void validateCoordinates(int[][] map, int startRow, int startColumn, int goalRow, int goalColumn) { + int columnCount = validateMaze(map); + if (startRow < 0 + || startRow >= map.length + || startColumn < 0 + || startColumn >= columnCount + || goalRow < 0 + || goalRow >= map.length + || goalColumn < 0 + || goalColumn >= columnCount) { + throw new IllegalArgumentException("Start and goal coordinates must be inside the maze."); } - return false; + } + + private static boolean isInBounds(int[][] map, int row, int column) { + return row >= 0 && row < map.length && column >= 0 && column < map[row].length; } } diff --git a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java index b8e77fb38bad..619180162520 100644 --- a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java +++ b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java @@ -1,6 +1,9 @@ package com.thealgorithms.backtracking; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; @@ -10,6 +13,49 @@ */ public class MazeRecursionTest { + @Test + public void testSmallMazeWithExplicitCoordinates() { + int[][] firstStrategyMaze = {{0, 0}, {0, 0}}; + int[][] secondStrategyMaze = {{0, 0}, {0, 0}}; + + assertArrayEquals(new int[][] {{2, 0}, {2, 2}}, MazeRecursion.solveMazeUsingFirstStrategy(firstStrategyMaze, 0, 0, 1, 1)); + assertArrayEquals(new int[][] {{2, 2}, {0, 2}}, MazeRecursion.solveMazeUsingSecondStrategy(secondStrategyMaze, 0, 0, 1, 1)); + } + + @Test + public void testLegacyMethodsReturnNullWhenFixedGoalIsOutsideMaze() { + assertNull(MazeRecursion.solveMazeUsingFirstStrategy(new int[2][2])); + assertNull(MazeRecursion.solveMazeUsingSecondStrategy(new int[2][2])); + } + + @Test + public void testLegacyMethodsAcceptAlreadyMarkedGoal() { + int[][] firstStrategyMaze = new int[7][6]; + int[][] secondStrategyMaze = new int[7][6]; + firstStrategyMaze[6][5] = 2; + secondStrategyMaze[6][5] = 2; + + assertSame(firstStrategyMaze, MazeRecursion.solveMazeUsingFirstStrategy(firstStrategyMaze)); + assertSame(secondStrategyMaze, MazeRecursion.solveMazeUsingSecondStrategy(secondStrategyMaze)); + } + + @Test + public void testNoPathExistsWithExplicitCoordinates() { + int[][] maze = {{0, 1}, {1, 0}}; + + assertNull(MazeRecursion.solveMazeUsingFirstStrategy(maze, 0, 0, 1, 1)); + } + + @Test + public void testExplicitCoordinatesMustBeInsideMaze() { + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[2][2], 0, 0, 2, 1)); + } + + @Test + public void testMazeMustBeRectangular() { + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0, 0}, {0}}, 0, 0, 1, 0)); + } + @Test public void testSolveMazeUsingFirstAndSecondStrategy() { int[][] map = new int[8][7]; From a0470f8f16deb7de121a0267404e5644af1f6adf Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Wed, 28 Oct 2026 23:50:41 +0530 Subject: [PATCH 15/18] fix: to update lint issue --- .../backtracking/MazeRecursion.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java index 0e13506df7ed..00c3fedca71f 100644 --- a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java +++ b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java @@ -62,9 +62,7 @@ public static int[][] solveMazeUsingSecondStrategy(int[][] map) { * @throws IllegalArgumentException if the maze is null, empty, not rectangular, * or a coordinate is outside the maze */ - public static int[][] solveMazeUsingFirstStrategy( - int[][] map, int startRow, int startColumn, int goalRow, int goalColumn - ) { + public static int[][] solveMazeUsingFirstStrategy(int[][] map, int startRow, int startColumn, int goalRow, int goalColumn) { validateCoordinates(map, startRow, startColumn, goalRow, goalColumn); return solve(map, startRow, startColumn, goalRow, goalColumn, FIRST_STRATEGY_DIRECTIONS) ? map : null; } @@ -81,9 +79,7 @@ public static int[][] solveMazeUsingFirstStrategy( * @throws IllegalArgumentException if the maze is null, empty, not rectangular, * or a coordinate is outside the maze */ - public static int[][] solveMazeUsingSecondStrategy( - int[][] map, int startRow, int startColumn, int goalRow, int goalColumn - ) { + public static int[][] solveMazeUsingSecondStrategy(int[][] map, int startRow, int startColumn, int goalRow, int goalColumn) { validateCoordinates(map, startRow, startColumn, goalRow, goalColumn); return solve(map, startRow, startColumn, goalRow, goalColumn, SECOND_STRATEGY_DIRECTIONS) ? map : null; } @@ -124,14 +120,7 @@ private static int validateMaze(int[][] map) { private static void validateCoordinates(int[][] map, int startRow, int startColumn, int goalRow, int goalColumn) { int columnCount = validateMaze(map); - if (startRow < 0 - || startRow >= map.length - || startColumn < 0 - || startColumn >= columnCount - || goalRow < 0 - || goalRow >= map.length - || goalColumn < 0 - || goalColumn >= columnCount) { + if (startRow < 0 || startRow >= map.length || startColumn < 0 || startColumn >= columnCount || goalRow < 0 || goalRow >= map.length || goalColumn < 0 || goalColumn >= columnCount) { throw new IllegalArgumentException("Start and goal coordinates must be inside the maze."); } } From 09b6ceb666199269f6140f404979ccd683839300 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Wed, 28 Oct 2026 23:51:14 +0530 Subject: [PATCH 16/18] Patch: to improve test coverage --- .../backtracking/MazeRecursionTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java index 619180162520..5486869775aa 100644 --- a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java +++ b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java @@ -51,11 +51,46 @@ public void testExplicitCoordinatesMustBeInsideMaze() { assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[2][2], 0, 0, 2, 1)); } + @Test + public void testEachStartAndGoalCoordinateMustBeInsideMaze() { + int[][] maze = new int[2][2]; + + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, -1, 0, 1, 1)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, 2, 0, 1, 1)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, 0, -1, 1, 1)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, 0, 2, 1, 1)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, 0, 0, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, 0, 0, 2, 1)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, 0, 0, 1, -1)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(maze, 0, 0, 1, 2)); + } + + @Test + public void testMazeMustNotBeNullOrEmpty() { + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(null)); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[0][0])); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {null})); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {new int[0]})); + assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0}, null})); + } + @Test public void testMazeMustBeRectangular() { assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0, 0}, {0}}, 0, 0, 1, 0)); } + @Test + public void testBlockedStartAndGoalHaveNoSolution() { + assertNull(MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{1, 0}, {0, 0}}, 0, 0, 1, 1)); + assertNull(MazeRecursion.solveMazeUsingSecondStrategy(new int[][] {{0, 0}, {0, 1}}, 0, 0, 1, 1)); + } + + @Test + public void testOpenSingleCellMazeIsAlreadySolved() { + assertArrayEquals(new int[][] {{2}}, MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0}}, 0, 0, 0, 0)); + assertArrayEquals(new int[][] {{2}}, MazeRecursion.solveMazeUsingSecondStrategy(new int[][] {{0}}, 0, 0, 0, 0)); + } + @Test public void testSolveMazeUsingFirstAndSecondStrategy() { int[][] map = new int[8][7]; From 1bb81bb01d28dc20d143f4136d4c50dad175a925 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Thu, 29 Oct 2026 00:04:50 +0530 Subject: [PATCH 17/18] test: to avoid Infer null dereference warning --- .../java/com/thealgorithms/backtracking/MazeRecursionTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java index 5486869775aa..0b29eb339ee2 100644 --- a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java +++ b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java @@ -71,7 +71,6 @@ public void testMazeMustNotBeNullOrEmpty() { assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[0][0])); assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {null})); assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {new int[0]})); - assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0}, null})); } @Test From c85fdb221848457f4c01f3c535abd08ee1d1fb79 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Thu, 29 Oct 2026 00:12:00 +0530 Subject: [PATCH 18/18] patch: to improve test coverage --- .../backtracking/MazeRecursionTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java index 0b29eb339ee2..7d8a0c66c262 100644 --- a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java +++ b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java @@ -24,10 +24,29 @@ public void testSmallMazeWithExplicitCoordinates() { @Test public void testLegacyMethodsReturnNullWhenFixedGoalIsOutsideMaze() { + assertNull(MazeRecursion.solveMazeUsingFirstStrategy(new int[1][1])); + assertNull(MazeRecursion.solveMazeUsingSecondStrategy(new int[1][1])); assertNull(MazeRecursion.solveMazeUsingFirstStrategy(new int[2][2])); assertNull(MazeRecursion.solveMazeUsingSecondStrategy(new int[2][2])); } + @Test + public void testLegacyMethodsReturnNullWhenFixedGoalRowOrColumnIsOutsideMaze() { + assertNull(MazeRecursion.solveMazeUsingFirstStrategy(new int[7][5])); + assertNull(MazeRecursion.solveMazeUsingSecondStrategy(new int[6][6])); + } + + @Test + public void testLegacyMethodsReturnNullWhenNoPathExists() { + int[][] firstStrategyMaze = new int[7][6]; + int[][] secondStrategyMaze = new int[7][6]; + firstStrategyMaze[1][1] = 1; + secondStrategyMaze[1][1] = 1; + + assertNull(MazeRecursion.solveMazeUsingFirstStrategy(firstStrategyMaze)); + assertNull(MazeRecursion.solveMazeUsingSecondStrategy(secondStrategyMaze)); + } + @Test public void testLegacyMethodsAcceptAlreadyMarkedGoal() { int[][] firstStrategyMaze = new int[7][6];