Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/main/java/com/thealgorithms/matrix/SearchMatrix.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.thealgorithms.matrix;

import java.util.Objects;

/**
* General-purpose search utilities for 2D matrices.
*
* <p>This class focuses on <b>membership queries</b> ("does the matrix contain the value?") for
* arbitrary 2D matrices. Unlike algorithms that rely on sorted rows/columns, these methods make no
* ordering assumptions.
*
* <p>Reference: Linear search
* https://en.wikipedia.org/wiki/Linear_search
*
* <p><b>Complexity</b>
*
* <ul>
* <li>Time: {@code O(m * n)} in the worst case (scan all elements).</li>
* <li>Space: {@code O(1)}.</li>
* </ul>
*/
public final class SearchMatrix {

private SearchMatrix() {
}

/**
* Searches for {@code target} in any 2D object matrix.
*
* <p>This method makes <b>no ordering assumptions</b> and performs a linear scan.
*
* <p>This method is null-safe:
*
* <ul>
* <li>If {@code matrix} is {@code null} or has zero rows, returns {@code false}.</li>
* <li>Null rows are treated as empty rows.</li>
* <li>Elements are compared using {@link Objects#equals(Object, Object)} (so {@code null}
* targets are supported).</li>
* </ul>
*
* @param matrix the input matrix (may be jagged)
* @param target the element to find (may be {@code null})
* @param <T> element type
* @return {@code true} if the target exists in the matrix, {@code false} otherwise
*/
public static <T> 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;
}
}
177 changes: 177 additions & 0 deletions src/main/java/com/thealgorithms/matrix/SearchSortedMatrix.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p><b>Assumptions</b>
*
* <ul>
* <li>Each row is sorted in ascending order (left &#8594; right).</li>
* <li>Each column is sorted in ascending order (top &#8595; bottom).</li>
* <li>The input is a rectangular matrix (not jagged) with non-null rows.</li>
* </ul>
*
* <p><b>Algorithm idea (search space reduction)</b>
*
* <p>Start in the top-right corner. At any position {@code (row, col)}:
*
* <ul>
* <li>If {@code matrix[row][col] > target}, then all values below in the same column are
* {@code >= matrix[row][col]} and therefore also {@code > target}; move left.</li>
* <li>If {@code matrix[row][col] < target}, then all values left in the same row are
* {@code <= matrix[row][col]} and therefore also {@code < target}; move down.</li>
* </ul>
*
* <p>Each move removes an entire row or column from consideration, so the search performs at most
* {@code rows + cols - 1} comparisons.
*
* <p>Reference: Saddleback search ("staircase" search)
* https://en.wikipedia.org/wiki/Saddleback_search
*
* <p><b>Alternatives</b>
*
* <ul>
* <li>Binary search in each row: {@code O(m * log(n))}.</li>
* <li>Binary search in each column: {@code O(n * log(m))}.</li>
* </ul>
*
* <p><b>Complexity</b>
*
* <ul>
* <li>Time: {@code O(m + n)} where {@code m} is the number of rows and {@code n} is the number of columns.</li>
* <li>Space: {@code O(1)}.</li>
* </ul>
*/
public final class SearchSortedMatrix {

private SearchSortedMatrix() {
}

/**
* Searches a matrix that is sorted ascending by row and by column.
*
* <p>This overload is intended for object matrices and uses the provided {@code comparator}.
* The matrix must be <b>rectangular</b> (not jagged) with non-null rows.
*
* <p>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 <T> 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 <T> boolean search(final T[][] matrix, final T target, final Comparator<? super T> 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.
*
* <p>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;
}
}
55 changes: 55 additions & 0 deletions src/test/java/com/thealgorithms/matrix/SearchMatrixTest.java
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading