在矩阵的任意子矩阵中找到最大元素

5
我正在给出一个大小为N x M的矩阵。对于一个长度为X、起始位置为(a, b)的子矩阵,我需要找到其中最大的元素。
我的方法是:
1. 按照问题描述进行操作: 使用简单的两个循环。
   for(i in range(a, a + x))
   for(j in range(b, b + x)) max = max(max,A[i][j]) // N * M
一点预先提示:
 1. Make a segment tree for every i in range(0, N)
 2. for i in range(a, a + x) query(b, b + x)    // N * logM

有没有仅具有O(log n)复杂度的更好的解决方案?

如果您可以对每列和每行进行排序,那么您可以使用以下方法:https://dev59.com/pHE95IYBdhLWcg3wGqAH - aviad
1
O(1)复杂度是可能的。只需在互联网上搜索“二维范围最小查询”即可。对于固定的X,存在一个简单的O(1)时间算法和O(MN)预处理。 - Evgeny Kluev
3个回答

6
一种基于稀疏表(Sparse Table)算法的方法:时间复杂度为 O( N x M x log(N) x log(M)) , O(1)

预处理时间 - O( N x M x log(N) x log(M))
查询时间 - O(1)

理解该方法需要掌握使用稀疏表算法计算一维RMQ的知识。

我们可以使用2D稀疏表算法来查找范围最小查询(Range Minimum Query)。

一维情况下的操作:
我们使用动态规划对长度为 2^k 的子数组进行预处理以获取RMQ。我们将保留一个数组 M[0, N-1][0, logN],其中M[i][j]是从i开始的子数组中最小值的索引。 为了计算M[i][j],我们必须在区间的前半部分和后半部分中搜索最小值。显然,小块的长度为2^(j-1),因此计算时的伪代码如下:

if (A[M[i][j-1]] < A[M[i + 2^(j-1) -1][j-1]])
    M[i][j] = M[i][j-1]
else 
    M[i][j] = M[i + 2^(j-1) -1][j-1]

这里的A是存储值的实际数组。一旦我们对这些值进行了预处理,让我们展示如何使用它们来计算RMQ(i, j)。思路是选择完全覆盖区间[i..j]的两个块,并在它们之间找到最小值。让k = [log(j - i + 1)]。为了计算RMQ(i, j),我们可以使用以下公式:

if (A[M[i][k]] <= A[M[j - 2^k + 1][k]])
    RMQ(i, j) = A[M[i][k]]
else 
    RMQ(i , j) = A[M[j - 2^k + 1][k]]


对于二维数组:
类似地,我们可以扩展上面的规则到二维数组中。在这里,我们使用动态规划预处理长度为2^K, 2^L的子矩阵以进行RMQ,并保持一个数组M[0,N-1][0, M-1][0, logN][0, logM]。其中M[x][y][k][l]是从[x , y]开始且长度为2^K, 2^L的子矩阵中最小值的索引。 计算M[x][y][k][l]的伪代码如下:

M[x][y][i][j] = GetMinimum(M[x][y][i-1][j-1], M[x + (2^(i-1))][y][i-1][j-1], M[x][y+(2^(j-1))][i-1][j-1], M[x + (2^(i-1))][y+(2^(j-1))][i-1][j-1])

这里的GetMinimum函数将返回所提供元素中最小元素的索引。 现在我们已经预处理完毕,让我们看看如何计算RMQ(x,y,x1,y1)。 这里[x,y]是子矩阵的起始点,[x1,y1]表示子矩阵的结束点,即子矩阵的右下角点。 在这里,我们必须选择四个子矩阵块,它们完全覆盖[x,y,x1,y1]并找到它们的最小值。 让k = [log(x1-x + 1)]l = [log(y1-y + 1)]。 为了计算RMQ(x,y,x1,y1),我们可以使用以下公式:

RMQ(x, y, x1, y1) = GetMinimum(M[x][y][k][l], M[x1 - (2^k) + 1][y][k][l], M[x][y1 - (2^l) + 1][k][l], M[x1 - (2^k) + 1][y1 - (2^l) + 1][k][l]);


以上逻辑的伪代码:

// remember Array 'M' store index of actual matrix 'P' so for comparing values in GetMinimum function compare the values of array 'P' not of array 'M' 
SparseMatrix(n , m){ // n , m is dimension of matrix.
    for i = 0 to 2^i <= n:
        for j = 0 to 2^j <= m:
            for x = 0 to x + 2^i -1 < n :
                for y = 0 to y + (2^j) -1 < m:
                    if i == 0 and j == 0:
                        M[x][y][i][j] = Pair(x , y) // store x, y
                    else if i == 0:
                        M[x][y][i][j] = GetMinimum(M[x][y][i][j-1], M[x][y+(2^(j-1))][i][j-1])
                    else if j == 0:
                        M[x][y][i][j] = GetMinimum(M[x][y][i-1][j], M[x+ (2^(i-1))][y][i-1][j])
                    else 
                        M[x][y][i][j] = GetMinimum(M[x][y][i-1][j-1], M[x + (2^(i-1))][y][i-1][j-1], M[x][y+(2^(j-1))][i-1][j-1], M[x + (2^(i-1))][y+(2^(j-1))][i-1][j-1]);
}
RMQ(x, y, x1, y1){
    k = log(x1 - x + 1)
    l = log(y1 - y + 1)
    ans = GetMinimum(M[x][y][k][l], M[x1 - (2^k) + 1][y][k][l], M[x][y1 - (2^l) + 1][k][l], M[x1 - (2^k) + 1][y1 - (2^l) + 1][k][l]);
    return P[ans->x][ans->y] // ans->x represent Row number stored in ans and similarly ans->y represent column stored in ans
}

你确定这个伪代码是正确的吗?我已经用C++实现了相同的代码,但它并没有正常工作,我得到了错误的答案。我认为有些地方不对。 - lavee_singh
你如何实现GetMinimum函数? - sudoer
1
这个 GetMinimum 函数是用来找到元素的最大值的。一开始我存储了索引,然后出现了一些错误,然后我存储了矩阵中的原始值。现在问题已经解决了。感谢您提供的绝妙答案。 - lavee_singh
如果在每个查询中计算kl的值,则查询时间将为O(logN + logM)。如果我们预先计算对数,那么查询时间将为O(1)。很好的答案,谢谢! - Sahil Arora

2

这里是C++的示例代码,根据@Chapta提供的伪代码编写而成,正如一些用户所请求的那样。

int M[1000][1000][10][10];
int **matrix;

void precompute_max(){
    for (int i = 0 ; (1<<i) <= n; i += 1){
        for(int j = 0 ; (1<<j) <= m ; j += 1){
            for (int x = 0 ; x + (1<<i) -1 < n; x+= 1){
                for (int y = 0 ;  y + (1<<j) -1 < m; y+= 1){
                    if (i == 0 and j == 0)
                        M[x][y][i][j] = matrix[x][y]; // store x, y
                    else if (i == 0)
                        M[x][y][i][j] = max(M[x][y][i][j-1], M[x][y+(1<<(j-1))][i][j-1]);
                    else if (j == 0)
                        M[x][y][i][j] = max(M[x][y][i-1][j], M[x+ (1<<(i-1))][y][i-1][j]);
                    else 
                        M[x][y][i][j] = max(M[x][y][i-1][j-1], M[x + (1<<(i-1))][y][i-1][j-1], M[x][y+(1<<(j-1))][i-1][j-1], M[x + (1<<(i-1))][y+(1<<(j-1))][i-1][j-1]);
                    // cout << "from i="<<x<<" j="<<y<<" of length="<<(1<<i)<<" and length="<<(1<<j) <<"max is: " << M[x][y][i][j] << endl;
                }
            }
        }
    }
}

int compute_max(int x, int y, int x1, int y1){
    int k = log2(x1 - x + 1);
    int l = log2(y1 - y + 1);
    // cout << "Value of k="<<k<<" l="<<l<<endl;
    int ans = max(M[x][y][k][l], M[x1 - (1<<k) + 1][y][k][l], M[x][y1 - (1<<l) + 1][k][l], M[x1 - (1<<k) + 1][y1 - (1<<l) + 1][k][l]);
    return ans;
}

这段代码首先预处理出二维稀疏表,然后以常数时间查询该表。附加信息:稀疏表存储的是最大元素而不是最大元素的索引。


0
据我所知,由于矩阵没有顺序,因此不能有O(logn的方法)。但是,如果您有一个顺序,使得每行从左到右升序排列,每列从上到下升序排列,则您知道A [a + x] [b + x](子矩阵的右下角单元格)是该子矩阵中最大的元素。因此,一旦排序矩阵,查找最大值只需要O(1)的时间。但是,如果矩阵未排序,则对矩阵进行排序将花费O(NxM log{NxM})的时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接