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 + * + *

+ */ +public final class SearchMatrix { + + private SearchMatrix() { + } + + /** + * Searches for {@code target} in any 2D object matrix. + * + *

This method makes no ordering assumptions and performs a linear scan. + * + *

This method is null-safe: + * + *

+ * + * @param matrix the input matrix (may be jagged) + * @param target the element to find (may be {@code null}) + * @param element type + * @return {@code true} if the target exists in the matrix, {@code false} otherwise + */ + public static boolean contains(final T[][] matrix, final T target) { + if (matrix == null || matrix.length == 0) { + return false; + } + + for (final T[] row : matrix) { + if (row == null || row.length == 0) { + continue; + } + for (final T value : row) { + if (Objects.equals(value, target)) { + return true; + } + } + } + + return false; + } +} diff --git a/src/main/java/com/thealgorithms/matrix/SearchSortedMatrix.java b/src/main/java/com/thealgorithms/matrix/SearchSortedMatrix.java new file mode 100644 index 000000000000..bed29a6817f5 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/SearchSortedMatrix.java @@ -0,0 +1,177 @@ +package com.thealgorithms.matrix; + +import java.util.Comparator; +import java.util.Objects; + +/** + * Provides an efficient search operation for a 2D matrix that is sorted in both directions. + * + *

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 + * + *

+ */ +public final class SearchSortedMatrix { + + private SearchSortedMatrix() { + } + + /** + * Searches a matrix that is sorted ascending by row and by column. + * + *

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 element type + * @return whether the target exists in the matrix + * @throws IllegalArgumentException if the matrix is jagged or contains null rows + * @throws NullPointerException if {@code comparator} is null + */ + public static boolean search(final T[][] matrix, final T target, final Comparator comparator) { + if (matrix == null) { + return false; + } + if (matrix.length == 0) { + return false; + } + if (matrix[0] == null) { + return false; + } + if (matrix[0].length == 0) { + return false; + } + + Objects.requireNonNull(comparator, "comparator"); + + final int rowCount = matrix.length; + final int colCount = matrix[0].length; + + for (final T[] 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 T value = matrix[rowIndex][colIndex]; + final int comparison = comparator.compare(value, target); + if (comparison == 0) { + return true; + } + if (comparison > 0) { + colIndex--; + } else { + rowIndex++; + } + } + + return false; + } + + /** + * Searches a matrix that is sorted ascending by row and by column. + * + *

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)); + } +}