有效地计算矩阵中元素的总和

时间:2010-02-17 01:35:18

标签: algorithm matrix

在一次采访中,我被问到是否给了一个n * m矩阵如何计算给定子矩阵中值的总和(由左上角,右下角坐标定义)。

我被告知我可以预处理矩阵。

我被告知矩阵可能是巨大的,因此子矩阵也是如此,因此算法必须高效。我发现了一点,并没有被告知最佳答案。

任何人都有一个好的答案?

5 个答案:

答案 0 :(得分:50)

这是Summed Area Tables的用途。 http://en.wikipedia.org/wiki/Summed_area_table

您的“预处理”步骤是构建一个相同大小的新矩阵,其中每个条目是该条目左上角的子矩阵的总和。任何子矩阵和都可以通过查找和混合SAT中的4个条目来计算。

编辑:以下是一个例子。

对于初始矩阵

0 1 4
2 3 2
1 2 7

SAT是

0 1 5
2 6 12
3 9 22

使用S(x,y)= a(x,y)+ S(x-1,y)+ S(x,y-1)-S(x-1,y-1)获得SAT ,

其中S是SAT矩阵,a是初始矩阵。

如果你想要右下2x2子矩阵的总和,答案将是22 + 0 - 3 - 5 = 14.这显然与3 + 2 + 2 + 7相同。无论大小如何对于矩阵,子矩阵的总和可以在4个查找和3个算术运算中找到。构建SAT是O(n),同样每个单元只需要4次查找和3次数学运算。

答案 1 :(得分:5)

您可以通过动态编程来完成。创建大小为n * m的矩阵dp。 并为每个i,j在哪里

1 <= i <= n , 1 <= j <= m 
dp[i][j] will be : 

dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + values[i][j]

对于每个查询,我们有lx,rx,ly,ry,其中lx和rx是左上角坐标,ly和ry是子矩阵的右下角坐标。

1 ≤ lxi ≤ rx ≤ n, 1 ≤ ly ≤ ry ≤ m

sum = dp[rx][ry]  - dp[lx - 1][ry] - dp[rx][ly - 1] + dp[lx-1][ly - 1]

查看图片以了解算法的工作原理。

OD = dp[rx][ry], OB = dp[lx - 1][ry], OC = dp[rx][ly - 1], OA = dp[lx - 1][ly - 1]

enter image description here

答案 2 :(得分:3)

创建一个新矩阵,其中条目(i,j)是原始矩阵中具有低于或等于ij的元素的总和。然后,要查找子矩阵中元素的总和,您可以使用和矩阵的子矩阵的角来使用恒定数量的基本运算。

特别是找到和矩阵的角top_left,bottom_left,top_right和bottom_right,其中前三个就在子矩阵之外,bottom_right就在里面。然后,你的金额将是

bottom_right + top_left - bottom_left - bottom_right

答案 3 :(得分:1)

以下是使用Summed Area Tables概念的C中的示例实现,如上面的一个答案所述。

同样的Python实现可以在下面的链接中找到 - http://www.ardendertat.com/2011/09/20/programming-interview-questions-2-matrix-region-sum/

#include<stdio.h>

int pre[3][3];

int arr[3][3] = {
                {0,1,4},
                {2,3,2},
                {1,2,7}
                };

void preprocess()
{
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            if(i>0 && j>0)
            {
                 pre[i][j] = arr[i][j] + pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1];
            }
            else if(i>0 && j==0)
            {
                pre[i][j] = arr[i][j] + pre[i-1][j];
            }
            else if(j>0 && i==0)
            {
                pre[i][j] = arr[i][j] + pre[i][j-1];
            }
            else
            {
                pre[i][j] = arr[i][j];
            }                    
        }
    }
}

int subsum(int x1, int y1, int x2, int y2)
{
    preprocess();

    int ans = pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1];
    return ans;
}

int main()
{            
    printf("%d\n",subsum(1,1,2,2));
    return 0;
}

答案 4 :(得分:0)

这应该有效。你总是必须遍历子矩阵中的每个元素来进行添加,这是最简单的方法。

*请注意,以下代码可能无法编译,但它在伪代码中是正确的


struct Coords{
    int x,y;
}

int SumSubMatrix(Coords topleft, Coords bottomright, int** matrix){
    int localsum = 0;
    for( int i = topleft.x; i <= bottomright.x; i++ ){
        for(int j = topleft.y; j <= bottomright.y; j++){
            localsum += matrix[i][j];
        }
    }
    return localsum;
}

编辑:另一种预处理方法是从包含行或列总​​和的原始数据创建另一个矩阵。这是一个例子: 原文:

0 1 4 
2 3 2
1 2 7

行矩阵:

0 1 5
2 5 7
1 3 10

列矩阵:

0 1 4
2 4 6
3 6 13

现在,只需获取端点x值并减去起点值,就像这样(基于行):


for( int i = topleft.y; i >= bottomright.y; i++ ){
    localsum += matrix2[bottomright.x][i] - matrix2[topleft.x][i];
}

现在,它是O(n)或O(m)