原地矩阵转置 微软面试题

昨天微软面试遇到的一道题:

       题意大约是这样:一个m×n的矩阵保存在一个一维数组里,然后要求空间复杂度不超过O(n)的条件下完成对它的转置,转置结果还是保存在这个数组里。

 

       因为如果没有空间的限制时,转置的复杂度为O(n×m),所以,拿到这题我就本能的想有没有什么巧妙的方法在O(n)的空间下时间也是O(m×n)。想了半天没想出来,只想到了一个O(m×m×n)的算法。

       算法思想大致是,每次完成一行的转置,把转置后的一行n个元素放到辅助空间,然后所有元素像后平移把空出来的位置填满,然后前面自然就是n个空位,然后把这n个元素插入。这样m-1次插入后,矩阵完成转置。

       算法是下如下:

      

#include<iostream>

using namespace std;

#define MAX_LEN 10000
int p[MAX_LEN];

//时间复杂度O(m*m*n)
//空间复杂度O(n)
void transposition(int *p,int m,int n)
{
    int s,e,step,len = m * n;
    int *buffer = new int[n];    //只使用了O(n)的空间

    for( s = 0; m > 1; m-- , s += n)
    {
        for( int i = 0; i < n; i++)                            
            buffer[i] = p[s+i*m];        

        for( e = len - m, step = 1; step < n; step++)
        {
            for(int i = 1 ; i < m; i++,e--)
                p[e] = p[e-step];
        }

        for( int i = 0; i < n; i++)
            p[s+i] = buffer[i];
    }

    delete []buffer;
}

int main()
{
    int n,m,cnt;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        cnt = 0;
        for(int i = 0; i < n; i++) 
            for(int j = 0; j < m; j++)
                p[i*m+j] = cnt++;

        transposition(p,m,n);
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
                printf("%4d",p[i*n+j]);
            printf("\n");
        }
    }
    return 0;
}

 

跟面试官描述完这个算法后,我自己觉得很没底,因为这个算法可以说一点儿也不巧妙,速度也不快。面试官好像也觉得这个算法效率不高。我说我再想想有没有更好的算法。

 

       然后面试官提示说这题用O(1)的空间复杂度也可以实现,由于一开始给了O(n)这个限制,我就一直在想如何充分利用这些空间提高效率。没有考虑O(1)来实现,但是被他这么一提示,我很快就反应到,矩阵转置后的每一个位置都是确定的,所以,可以算出来每一个点应该移到哪里,但是不能直接移过去,因为那个位置有别的点,那就把那个点移到它该去的位置,依次类推,必然形成一个环,经过这么一次循环,这一个环上的元素就都在自己该在的位置了。多么完美啊,这不就是O(m×n)么,但是仔细想想是不对的,因为一个矩阵可能包含多个环。那么就要判断当前位置是否在已经遍历过的环上了。

       然后我就一直围绕着O(m×n)想怎么判断,如何能找到环的规律,想了半天也没想出来。

       最后,面试结束,面试官描述了一下他的算法,从左到右遍历,如果发现有一个节点的环中存在它右边的节点,则这个环遍历过。当时比较混乱,只是大致知道他的意思,回来之后写出了如下代码:

 

#include<iostream>

using namespace std;

#define MAX_LEN 10000
int p[MAX_LEN];

//时间复杂度O((m*n)^2)
//空间复杂度O(1)

void transposition(int *p,int m,int n)
{
    int len = m * n,a,b,t;                                    
    for(int i = 0; i < len;  i++)                            //cnt==len代表所有位置都已转置无需再循环
    {
        for( b = m*(i%n)+(i/n); b > i; b = m*(b%n)+(b/n));    
        if( b == i )                                        //如果这个环回到了一个小于i的位置,证明这个环已经遍历过
        {
            t = p[i];
            for( a = i, b = m*(i%n)+(i/n); b != i; b = m*(a%n)+(a/n)) //将这个环循按位置排好
            {
                p[a] = p[b];
                a = b;
            }
            p[a] = t;
        }
    }
}

int main()
{
    int n,m,cnt;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        cnt = 0;
        for(int i = 0; i < n; i++) 
            for(int j = 0; j < m; j++)
                p[i*m+j] = cnt++;

        transposition(p,m,n);
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
                printf("%4d",p[i*n+j]);
            printf("\n");
        }
    }
    return 0;
}

 

 

但是写完之后我发现这个代码的复杂度是O((m×n)^2)的,如果分析的仔细一点来说,这个算法的复杂度是m×n×(所有环的长度的平方和),由于环的分布我们是不知道的,起码我是没有找到规律,所有环的长度和m×n,所以这个复杂度的上界为O((m×n)^2)。

如果有神牛可以找到环的分布规律,证明一个更低的复杂度下界请给我留言。

如果有神牛可以找到每一个环的最左元素的递推式,这个问题就可以在O(m×n)的时间复杂度内解,那么你务必要给我留言。

所以,综上所述,这个算法确实比我的算法还要慢。

 

然后我就在网上各种百度,矩阵原地转置,也没有发现O(1)空间复杂度低于O((m*n)^2)的算法,如果有神牛知道,请给我留言。

 

总结:面试完,问了问在微软的朋友,朋友说这样评分,先看有没有给出正确答案,然后再看能不能在正确答案的基础上进行优化。我觉得这次面试发挥不好主要就是选错了答题策略,一开始就想最优的解法是什么。应该先快速的想一个简单而且正确的解法,回答面试官,如果面试官问你能否优化,再去考虑优化,因为有的题可能就没什么好优化的,比方说这题。我最初的想法好像也不像我想象中的效率那么低,到目前为止我也没有找到更好的方法。

posted on 2013-04-22 17:30  十指之间  阅读(1937)  评论(0编辑  收藏  举报

导航