diff --git a/src/main/java/com/thealgorithms/matrix/SearchMatrix.java b/src/main/java/com/thealgorithms/matrix/SearchMatrix.java new file mode 100644 index 000000000000..5fd14813ad12 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/SearchMatrix.java @@ -0,0 +1,64 @@ +package com.thealgorithms.matrix; + +import java.util.Objects; + +/** + * General-purpose search utilities for 2D matrices. + * + *
This class focuses on membership queries ("does the matrix contain the value?") for + * arbitrary 2D matrices. Unlike algorithms that rely on sorted rows/columns, these methods make no + * ordering assumptions. + * + *
Reference: Linear search + * https://en.wikipedia.org/wiki/Linear_search + * + *
Complexity + * + *
This method makes no ordering assumptions and performs a linear scan. + * + *
This method is null-safe: + * + *
Assumptions + * + *
Algorithm idea (search space reduction) + * + *
Start in the top-right corner. At any position {@code (row, col)}: + * + *
Each move removes an entire row or column from consideration, so the search performs at most + * {@code rows + cols - 1} comparisons. + * + *
Reference: Saddleback search ("staircase" search) + * https://en.wikipedia.org/wiki/Saddleback_search + * + *
Alternatives + * + *
Complexity + * + *
This overload is intended for object matrices and uses the provided {@code comparator}. + * The matrix must be rectangular (not jagged) with non-null rows. + * + *
Note: If the matrix contains {@code null} elements (or {@code target} is {@code null}),
+ * the {@code comparator} must define how to order {@code null} values (for example,
+ * {@link Comparator#nullsFirst(Comparator)}).
+ *
+ * @param matrix the input rectangular matrix
+ * @param target the value to locate
+ * @param comparator comparator consistent with the matrix sort order
+ * @param Returns {@code true} if {@code target} exists in the matrix, {@code false} otherwise.
+ *
+ * @param matrix the input matrix
+ * @param target the value to locate
+ * @return whether the target exists in the matrix
+ * @throws IllegalArgumentException if the matrix is jagged or contains null rows
+ */
+ public static boolean search(final int[][] matrix, final int target) {
+ if (matrix == null) {
+ return false;
+ }
+ if (matrix.length == 0) {
+ return false;
+ }
+ if (matrix[0] == null) {
+ return false;
+ }
+ if (matrix[0].length == 0) {
+ return false;
+ }
+
+ final int rowCount = matrix.length;
+ final int colCount = matrix[0].length;
+
+ for (final int[] row : matrix) {
+ if (row == null) {
+ throw new IllegalArgumentException("Matrix must not contain null rows");
+ }
+ if (row.length != colCount) {
+ throw new IllegalArgumentException("Matrix must be rectangular (not jagged)");
+ }
+ }
+
+ int rowIndex = 0;
+ int colIndex = colCount - 1;
+
+ while (rowIndex < rowCount) {
+ if (colIndex < 0) {
+ break;
+ }
+ final int value = matrix[rowIndex][colIndex];
+ if (value == target) {
+ return true;
+ }
+ if (value > target) {
+ colIndex--;
+ } else {
+ rowIndex++;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/matrix/SearchMatrixTest.java b/src/test/java/com/thealgorithms/matrix/SearchMatrixTest.java
new file mode 100644
index 000000000000..e4a54cd89f86
--- /dev/null
+++ b/src/test/java/com/thealgorithms/matrix/SearchMatrixTest.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.matrix;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class SearchMatrixTest {
+
+ @Test
+ void nullMatrixReturnsFalse() {
+ assertFalse(SearchMatrix.contains(null, 1));
+ }
+
+ @Test
+ void emptyMatrixReturnsFalse() {
+ assertFalse(SearchMatrix.contains(new Integer[0][], 1));
+ }
+
+ @Test
+ void findsElementInRectangularMatrix() {
+ final Integer[][] matrix = {
+ {1, 2, 3},
+ {4, 5, 6},
+ };
+
+ assertTrue(SearchMatrix.contains(matrix, 5));
+ assertFalse(SearchMatrix.contains(matrix, 7));
+ }
+
+ @Test
+ void supportsNullTargetAndNullElements() {
+ final String[][] matrix = {
+ {"a", null},
+ {"b", "c"},
+ };
+
+ assertTrue(SearchMatrix.contains(matrix, null));
+ assertTrue(SearchMatrix.contains(matrix, "c"));
+ assertFalse(SearchMatrix.contains(matrix, "d"));
+ }
+
+ @Test
+ void supportsJaggedMatricesAndNullRows() {
+ final Integer[][] matrix = {
+ {1, 2, 3},
+ null,
+ {},
+ {4},
+ };
+
+ assertTrue(SearchMatrix.contains(matrix, 4));
+ assertFalse(SearchMatrix.contains(matrix, 5));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/matrix/SearchSortedMatrixTest.java b/src/test/java/com/thealgorithms/matrix/SearchSortedMatrixTest.java
new file mode 100644
index 000000000000..64b4a02720b8
--- /dev/null
+++ b/src/test/java/com/thealgorithms/matrix/SearchSortedMatrixTest.java
@@ -0,0 +1,209 @@
+package com.thealgorithms.matrix;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Comparator;
+import org.junit.jupiter.api.Test;
+
+class SearchSortedMatrixTest {
+
+ @Test
+ void nullMatrixReturnsFalse() {
+ final int[][] matrix = null;
+ assertFalse(SearchSortedMatrix.search(matrix, 42));
+ }
+
+ @Test
+ void emptyMatrixReturnsFalse() {
+ assertFalse(SearchSortedMatrix.search(new int[0][], 42));
+ }
+
+ @Test
+ void emptyFirstRowReturnsFalse() {
+ assertFalse(SearchSortedMatrix.search(new int[][] {{}}, 42));
+ }
+
+ @Test
+ void nullFirstRowReturnsFalse() {
+ assertFalse(SearchSortedMatrix.search(new int[][] {null}, 42));
+ }
+
+ @Test
+ void findsExistingTargetInTypicalMatrix() {
+ final int[][] matrix = {
+ {1, 4, 7, 11, 15},
+ {2, 5, 8, 12, 19},
+ {3, 6, 9, 16, 22},
+ {10, 13, 14, 17, 24},
+ {18, 21, 23, 26, 30},
+ };
+
+ assertTrue(SearchSortedMatrix.search(matrix, 5));
+ assertTrue(SearchSortedMatrix.search(matrix, 30));
+ assertTrue(SearchSortedMatrix.search(matrix, 1));
+ }
+
+ @Test
+ void intSearchCoversAllComparisonBranches() {
+ final int[][] matrix = {
+ {1, 4},
+ {2, 5},
+ };
+
+ assertTrue(SearchSortedMatrix.search(matrix, 2));
+ }
+
+ @Test
+ void genericSearchFindsExistingTarget() {
+ final Integer[][] matrix = {
+ {1, 4, 7, 11, 15},
+ {2, 5, 8, 12, 19},
+ {3, 6, 9, 16, 22},
+ {10, 13, 14, 17, 24},
+ {18, 21, 23, 26, 30},
+ };
+
+ assertTrue(SearchSortedMatrix.search(matrix, 16, Comparator.naturalOrder()));
+ assertFalse(SearchSortedMatrix.search(matrix, 20, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericTargetSmallerThanAllValuesReturnsFalse() {
+ final Integer[][] matrix = {
+ {1, 4},
+ {2, 5},
+ };
+
+ assertFalse(SearchSortedMatrix.search(matrix, 0, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericSearchCoversAllComparisonBranches() {
+ final Integer[][] matrix = {
+ {1, 4},
+ {2, 5},
+ };
+
+ assertTrue(SearchSortedMatrix.search(matrix, 2, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericNullMatrixReturnsFalse() {
+ assertFalse(SearchSortedMatrix.search((Integer[][]) null, 42, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericEmptyMatrixReturnsFalse() {
+ assertFalse(SearchSortedMatrix.search(new Integer[0][], 42, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericEmptyFirstRowReturnsFalse() {
+ assertFalse(SearchSortedMatrix.search(new Integer[][] {{}}, 42, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericNullFirstRowReturnsFalse() {
+ assertFalse(SearchSortedMatrix.search(new Integer[][] {null}, 42, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericRejectsJaggedMatrix() {
+ final Integer[][] jagged = {
+ {1, 2, 3},
+ {4, 5},
+ };
+
+ assertThrows(IllegalArgumentException.class, () -> SearchSortedMatrix.search(jagged, 5, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericRejectsNullRow() {
+ final Integer[][] hasNullRow = {
+ {1, 2, 3},
+ null,
+ };
+
+ assertThrows(IllegalArgumentException.class, () -> SearchSortedMatrix.search(hasNullRow, 2, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void genericNullComparatorThrows() {
+ final Integer[][] matrix = {
+ {1, 2, 3},
+ };
+
+ assertThrows(NullPointerException.class, () -> SearchSortedMatrix.search(matrix, 2, null));
+ }
+
+ @Test
+ void returnsFalseWhenTargetDoesNotExist() {
+ final int[][] matrix = {
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ };
+
+ assertFalse(SearchSortedMatrix.search(matrix, 0));
+ assertFalse(SearchSortedMatrix.search(matrix, 10));
+ }
+
+ @Test
+ void worksForSingleRowMatrix() {
+ final int[][] matrix = {
+ {1, 3, 5, 7, 9},
+ };
+
+ assertTrue(SearchSortedMatrix.search(matrix, 7));
+ assertFalse(SearchSortedMatrix.search(matrix, 8));
+ }
+
+ @Test
+ void worksForSingleColumnMatrix() {
+ final int[][] matrix = {
+ {1},
+ {3},
+ {5},
+ {7},
+ };
+
+ assertTrue(SearchSortedMatrix.search(matrix, 1));
+ assertTrue(SearchSortedMatrix.search(matrix, 7));
+ assertFalse(SearchSortedMatrix.search(matrix, 2));
+ }
+
+ @Test
+ void handlesNegativesAndDuplicates() {
+ final int[][] matrix = {
+ {-5, -3, -3, 0},
+ {-4, -2, 1, 2},
+ {-1, 3, 3, 4},
+ };
+
+ assertTrue(SearchSortedMatrix.search(matrix, -3));
+ assertTrue(SearchSortedMatrix.search(matrix, 3));
+ assertFalse(SearchSortedMatrix.search(matrix, 6));
+ }
+
+ @Test
+ void rejectsJaggedMatrix() {
+ final int[][] jagged = {
+ {1, 2, 3},
+ {4, 5},
+ };
+
+ assertThrows(IllegalArgumentException.class, () -> SearchSortedMatrix.search(jagged, 5));
+ }
+
+ @Test
+ void rejectsNullRow() {
+ final int[][] hasNullRow = {
+ {1, 2, 3},
+ null,
+ };
+
+ assertThrows(IllegalArgumentException.class, () -> SearchSortedMatrix.search(hasNullRow, 2));
+ }
+}