Skip to content

Commit 4c17ca7

Browse files
committed
Add 2D matrix search utilities
1 parent 44e25cb commit 4c17ca7

File tree

4 files changed

+403
-0
lines changed

4 files changed

+403
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.thealgorithms.matrix;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* General-purpose search utilities for 2D matrices.
7+
*
8+
* <p>This class focuses on <b>membership queries</b> ("does the matrix contain the value?") for
9+
* arbitrary 2D matrices. Unlike algorithms that rely on sorted rows/columns, these methods make no
10+
* ordering assumptions.
11+
*
12+
* <p>Reference: Linear search
13+
* https://en.wikipedia.org/wiki/Linear_search
14+
*
15+
* <p><b>Complexity</b>
16+
*
17+
* <ul>
18+
* <li>Time: {@code O(m * n)} in the worst case (scan all elements).</li>
19+
* <li>Space: {@code O(1)}.</li>
20+
* </ul>
21+
*/
22+
public final class SearchMatrix {
23+
24+
private SearchMatrix() {
25+
}
26+
27+
/**
28+
* Searches for {@code target} in any 2D object matrix.
29+
*
30+
* <p>This method makes <b>no ordering assumptions</b> and performs a linear scan.
31+
*
32+
* <p>This method is null-safe:
33+
*
34+
* <ul>
35+
* <li>If {@code matrix} is {@code null} or has zero rows, returns {@code false}.</li>
36+
* <li>Null rows are treated as empty rows.</li>
37+
* <li>Elements are compared using {@link Objects#equals(Object, Object)} (so {@code null}
38+
* targets are supported).</li>
39+
* </ul>
40+
*
41+
* @param matrix the input matrix (may be jagged)
42+
* @param target the element to find (may be {@code null})
43+
* @param <T> element type
44+
* @return {@code true} if the target exists in the matrix, {@code false} otherwise
45+
*/
46+
public static <T> boolean contains(final T[][] matrix, final T target) {
47+
if (matrix == null || matrix.length == 0) {
48+
return false;
49+
}
50+
51+
for (final T[] row : matrix) {
52+
if (row == null || row.length == 0) {
53+
continue;
54+
}
55+
for (final T value : row) {
56+
if (Objects.equals(value, target)) {
57+
return true;
58+
}
59+
}
60+
}
61+
62+
return false;
63+
}
64+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package com.thealgorithms.matrix;
2+
3+
import java.util.Comparator;
4+
import java.util.Objects;
5+
6+
/**
7+
* Provides an efficient search operation for a 2D matrix that is sorted in both directions.
8+
*
9+
* <p><b>Assumptions</b>
10+
*
11+
* <ul>
12+
* <li>Each row is sorted in ascending order (left &#8594; right).</li>
13+
* <li>Each column is sorted in ascending order (top &#8595; bottom).</li>
14+
* <li>The input is a rectangular matrix (not jagged) with non-null rows.</li>
15+
* </ul>
16+
*
17+
* <p><b>Algorithm idea (search space reduction)</b>
18+
*
19+
* <p>Start in the top-right corner. At any position {@code (row, col)}:
20+
*
21+
* <ul>
22+
* <li>If {@code matrix[row][col] > target}, then all values below in the same column are
23+
* {@code >= matrix[row][col]} and therefore also {@code > target}; move left.</li>
24+
* <li>If {@code matrix[row][col] < target}, then all values left in the same row are
25+
* {@code <= matrix[row][col]} and therefore also {@code < target}; move down.</li>
26+
* </ul>
27+
*
28+
* <p>Each move removes an entire row or column from consideration, so the search performs at most
29+
* {@code rows + cols - 1} comparisons.
30+
*
31+
* <p>Reference: Saddleback search ("staircase" search)
32+
* https://en.wikipedia.org/wiki/Saddleback_search
33+
*
34+
* <p><b>Alternatives</b>
35+
*
36+
* <ul>
37+
* <li>Binary search in each row: {@code O(m * log(n))}.</li>
38+
* <li>Binary search in each column: {@code O(n * log(m))}.</li>
39+
* </ul>
40+
*
41+
* <p><b>Complexity</b>
42+
*
43+
* <ul>
44+
* <li>Time: {@code O(m + n)} where {@code m} is the number of rows and {@code n} is the number of columns.</li>
45+
* <li>Space: {@code O(1)}.</li>
46+
* </ul>
47+
*/
48+
public final class SearchSortedMatrix {
49+
50+
private SearchSortedMatrix() {
51+
}
52+
53+
/**
54+
* Searches a matrix that is sorted ascending by row and by column.
55+
*
56+
* <p>This overload is intended for object matrices and uses the provided {@code comparator}.
57+
* The matrix must be <b>rectangular</b> (not jagged) with non-null rows.
58+
*
59+
* <p>Note: If the matrix contains {@code null} elements (or {@code target} is {@code null}),
60+
* the {@code comparator} must define how to order {@code null} values (for example,
61+
* {@link Comparator#nullsFirst(Comparator)}).
62+
*
63+
* @param matrix the input rectangular matrix
64+
* @param target the value to locate
65+
* @param comparator comparator consistent with the matrix sort order
66+
* @param <T> element type
67+
* @return whether the target exists in the matrix
68+
* @throws IllegalArgumentException if the matrix is jagged or contains null rows
69+
* @throws NullPointerException if {@code comparator} is null
70+
*/
71+
public static <T> boolean search(final T[][] matrix, final T target, final Comparator<? super T> comparator) {
72+
if (matrix == null || matrix.length == 0) {
73+
return false;
74+
}
75+
if (matrix[0] == null || matrix[0].length == 0) {
76+
return false;
77+
}
78+
79+
Objects.requireNonNull(comparator, "comparator");
80+
81+
final int rowCount = matrix.length;
82+
final int colCount = matrix[0].length;
83+
84+
for (final T[] row : matrix) {
85+
if (row == null) {
86+
throw new IllegalArgumentException("Matrix must not contain null rows");
87+
}
88+
if (row.length != colCount) {
89+
throw new IllegalArgumentException("Matrix must be rectangular (not jagged)");
90+
}
91+
}
92+
93+
int rowIndex = 0;
94+
int colIndex = colCount - 1;
95+
96+
while (rowIndex < rowCount && colIndex >= 0) {
97+
final T value = matrix[rowIndex][colIndex];
98+
final int comparison = comparator.compare(value, target);
99+
if (comparison == 0) {
100+
return true;
101+
}
102+
if (comparison > 0) {
103+
colIndex--;
104+
} else {
105+
rowIndex++;
106+
}
107+
}
108+
109+
return false;
110+
}
111+
112+
/**
113+
* Searches a matrix that is sorted ascending by row and by column.
114+
*
115+
* <p>Returns {@code true} if {@code target} exists in the matrix, {@code false} otherwise.
116+
*
117+
* @param matrix the input matrix
118+
* @param target the value to locate
119+
* @return whether the target exists in the matrix
120+
* @throws IllegalArgumentException if the matrix is jagged or contains null rows
121+
*/
122+
public static boolean search(final int[][] matrix, final int target) {
123+
if (matrix == null || matrix.length == 0) {
124+
return false;
125+
}
126+
if (matrix[0] == null || matrix[0].length == 0) {
127+
return false;
128+
}
129+
130+
final int rowCount = matrix.length;
131+
final int colCount = matrix[0].length;
132+
133+
for (final int[] row : matrix) {
134+
if (row == null) {
135+
throw new IllegalArgumentException("Matrix must not contain null rows");
136+
}
137+
if (row.length != colCount) {
138+
throw new IllegalArgumentException("Matrix must be rectangular (not jagged)");
139+
}
140+
}
141+
142+
int rowIndex = 0;
143+
int colIndex = colCount - 1;
144+
145+
while (rowIndex < rowCount && colIndex >= 0) {
146+
final int value = matrix[rowIndex][colIndex];
147+
if (value == target) {
148+
return true;
149+
}
150+
if (value > target) {
151+
colIndex--;
152+
} else {
153+
rowIndex++;
154+
}
155+
}
156+
157+
return false;
158+
}
159+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.thealgorithms.matrix;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
class SearchMatrixTest {
9+
10+
@Test
11+
void nullMatrixReturnsFalse() {
12+
assertFalse(SearchMatrix.contains(null, 1));
13+
}
14+
15+
@Test
16+
void emptyMatrixReturnsFalse() {
17+
assertFalse(SearchMatrix.contains(new Integer[0][], 1));
18+
}
19+
20+
@Test
21+
void findsElementInRectangularMatrix() {
22+
final Integer[][] matrix = {
23+
{1, 2, 3},
24+
{4, 5, 6},
25+
};
26+
27+
assertTrue(SearchMatrix.contains(matrix, 5));
28+
assertFalse(SearchMatrix.contains(matrix, 7));
29+
}
30+
31+
@Test
32+
void supportsNullTargetAndNullElements() {
33+
final String[][] matrix = {
34+
{"a", null},
35+
{"b", "c"},
36+
};
37+
38+
assertTrue(SearchMatrix.contains(matrix, null));
39+
assertTrue(SearchMatrix.contains(matrix, "c"));
40+
assertFalse(SearchMatrix.contains(matrix, "d"));
41+
}
42+
43+
@Test
44+
void supportsJaggedMatricesAndNullRows() {
45+
final Integer[][] matrix = {
46+
{1, 2, 3},
47+
null,
48+
{},
49+
{4},
50+
};
51+
52+
assertTrue(SearchMatrix.contains(matrix, 4));
53+
assertFalse(SearchMatrix.contains(matrix, 5));
54+
}
55+
}

0 commit comments

Comments
 (0)