Searching & SortingProblem 20 of 36
Rasta and Kheshtak
Problem Statement
Given two matrices A (n×m) and B (x×y), determine if matrix B is a submatrix of matrix A. A submatrix is a contiguous 2D block within the larger matrix.
Constraints:
- 1 ≤ n, m ≤ 1000
- 1 ≤ x ≤ n, 1 ≤ y ≤ m
- Matrix elements can be any integers
Example:
- Input:
A = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] B = [[6, 7], [10, 11]] - Output:
true - Explanation: B appears in A starting at position (1, 1)
Example 2:
- Input:
A = [[1, 2, 3], [4, 5, 6]] B = [[1, 3], [4, 6]] - Output:
false - Explanation: B is not a contiguous submatrix of A
Approach 1: Brute Force (Check All Positions)
Intuition
Try every possible starting position in matrix A and check if the submatrix starting at that position matches B.
Algorithm
- For each possible starting position (i, j) in A:
- Where i can be from 0 to n-x and j can be from 0 to m-y
- Check if the x×y submatrix starting at (i, j) matches B
- If match found, return true
- Return false if no match found
java
public class Solution {
public boolean isSubmatrix(int[][] A, int[][] B) {
int n = A.length, m = A[0].length;
int x = B.length, y = B[0].length;
// Check if B can fit in A
if (x > n || y > m) return false;
// Try all starting positions
for (int i = 0; i <= n - x; i++) {
for (int j = 0; j <= m - y; j++) {
boolean match = true;
// Check if submatrix matches B
outer:
for (int bi = 0; bi < x; bi++) {
for (int bj = 0; bj < y; bj++) {
if (A[i + bi][j + bj] != B[bi][bj]) {
match = false;
break outer;
}
}
}
if (match) return true;
}
}
return false;
}
}Complexity Analysis
- Time Complexity: O(n² × m²) in worst case - O((n-x)×(m-y)×x×y)
- Space Complexity: O(1) - No extra space used
Approach 2: Hashing with Rabin-Karp (Optimal)
Intuition
Use 2D rolling hash (Rabin-Karp) to efficiently compare submatrices. First hash each row, then use column-wise hashing on row hashes.
Key Observations:
- Compute hash for each row of length y using polynomial hashing
- Stack row hashes and compute column hash of height x
- Use rolling hash to slide the window efficiently
Algorithm
- Compute hash for pattern B
- For each valid column position j:
- Compute row hashes of length y for each row
- Use rolling hash vertically to find matching column hash
- Verify match to handle hash collisions
java
public class SubmatrixSearch {
private static final long MOD = 1000000007;
private static final long BASE1 = 31;
private static final long BASE2 = 37;
private long power(long base, int exp) {
long result = 1;
base %= MOD;
while (exp > 0) {
if ((exp & 1) == 1) result = (result * base) % MOD;
base = (base * base) % MOD;
exp >>= 1;
}
return result;
}
public boolean isSubmatrix(int[][] A, int[][] B) {
int n = A.length, m = A[0].length;
int x = B.length, y = B[0].length;
if (x > n || y > m) return false;
// Compute hash for pattern B
long patternHash = 0;
for (int i = 0; i < x; i++) {
long rowHash = 0;
for (int j = 0; j < y; j++) {
rowHash = (rowHash * BASE1 + B[i][j] + 1) % MOD;
}
patternHash = (patternHash * BASE2 + rowHash) % MOD;
}
// Precompute powers
long pow1 = power(BASE1, y - 1);
long pow2 = power(BASE2, x - 1);
// Compute row hashes for matrix A
long[][] rowHashes = new long[n][m - y + 1];
for (int i = 0; i < n; i++) {
long hash = 0;
for (int j = 0; j < y; j++) {
hash = (hash * BASE1 + A[i][j] + 1) % MOD;
}
rowHashes[i][0] = hash;
for (int j = 1; j <= m - y; j++) {
hash = (((hash - (A[i][j-1] + 1) * pow1 % MOD + MOD) % MOD) * BASE1 + A[i][j+y-1] + 1) % MOD;
rowHashes[i][j] = hash;
}
}
// Check column-wise hashes
for (int j = 0; j <= m - y; j++) {
long colHash = 0;
for (int i = 0; i < x; i++) {
colHash = (colHash * BASE2 + rowHashes[i][j]) % MOD;
}
if (colHash == patternHash && verify(A, B, 0, j)) {
return true;
}
for (int i = 1; i <= n - x; i++) {
colHash = (((colHash - rowHashes[i-1][j] * pow2 % MOD + MOD) % MOD) * BASE2 + rowHashes[i+x-1][j]) % MOD;
if (colHash == patternHash && verify(A, B, i, j)) {
return true;
}
}
}
return false;
}
private boolean verify(int[][] A, int[][] B, int si, int sj) {
int x = B.length, y = B[0].length;
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
if (A[si + i][sj + j] != B[i][j]) {
return false;
}
}
}
return true;
}
}Dry Run Example
A = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]]
B = [[6, 7],
[10, 11]]
Step 1: Compute pattern hash for B
Row 0: hash = 6*31 + 7 = 193
Row 1: hash = 10*31 + 11 = 321
Pattern hash = 193*37 + 321 = 7462
Step 2: Compute row hashes for A (window size 2)
Row 0: [1*31+2=33, 2*31+3=65, 3*31+4=97]
Row 1: [5*31+6=161, 6*31+7=193, 7*31+8=225]
Row 2: [9*31+10=289, 10*31+11=321, 11*31+12=353]
Step 3: Check column hashes (window height 2)
At j=1:
colHash = 193*37 + 321 = 7462 = patternHash
Verify match: A[1][1..2] and A[2][1..2] == B
Match found!
Result: true
Complexity Analysis
- Time Complexity: O(n × m) - Single pass with rolling hash
- Space Complexity: O(n × m) - For storing row hashes
Key Takeaways
- 2D Rolling Hash: Extend Rabin-Karp to 2D by hashing rows first, then columns
- Double Hashing: Use different bases for row and column to reduce collisions
- Verification: Always verify matches due to possible hash collisions
- Rolling Hash: Allows O(1) window sliding instead of O(window_size) recomputation
- Preprocessing: Computing row hashes first enables efficient column-wise sliding
- Applications: Pattern matching, image processing, plagiarism detection