旋转矩阵n次

6
我在HackerRank上解决问题时遇到了这个问题。 问题陈述 您将获得一个尺寸为MxN的2D矩阵a和一个正整数R。您必须旋转矩阵R次并打印结果矩阵。旋转应该是逆时针方向的。
4x5矩阵的旋转如下图所示。请注意,在一次旋转中,您只需将元素向前移动一步(有关更多详细信息,请参见示例测试)。 enter image description here 保证M和N的最小值将是偶数。 输入 第一行包含三个空格分隔的整数M、N和R,其中M是矩阵中的行数,N是列数,R是必须旋转矩阵的次数。 然后是M行,每行包含N个以空格分隔的正整数。这些M行代表矩阵。 输出 打印旋转矩阵。 约束条件
2 <= M, N <= 300 
1 <= R <= 10^9 
min(M, N) % 2 == 0 
1 <= aij <= 108, where i ∈ [1..M] & j ∈ [1..N]'

我尝试的做法是将圆存储在一个一维数组中。就像这样。
 while(true)
    {
        k = 0;
        for(int j = left; j <= right; ++j) {temp[k] = a[top][j]; ++k;}
        top++;
        if(top > down || left > right) break;

        for(int i = top; i <= down; ++i) {temp[k] = a[i][right]; ++k;}
        right--;
        if(top > down || left > right) break;

        for(int j = right; j >= left; --j) {temp[k] = a[down][j] ; ++k;}
        down--;
        if(top > down || left > right) break;

        for(int i = down; i >= top; --i) {temp[k] = a[i][left]; ++k;}
        left++;
        if(top > down || left > right) break;
    }

然后,我可以通过计算其长度对 R 取模来轻松旋转 1D 矩阵。但是,如何将其放回矩阵形式?再次使用循环可能会导致超时。
请不要提供代码,只给出建议。我想自己完成。
解决方案已创建:
#include <iostream>
using namespace std;



int main() {
int m,n,r;
cin>>m>>n>>r;
int a[300][300];
for(int i = 0 ; i < m ; ++i){
    for(int j = 0; j < n ; ++j)
        cin>>a[i][j];
}

int left = 0;
int right = n-1;
int top = 0;
int down = m-1;
int tleft = 0;
int tright = n-1;
int ttop = 0;
int tdown = m-1;
int b[300][300];
int k,size;
int temp[1200];

while(true){
    k=0;
    for(int i = left; i <= right ; ++i)
    {
        temp[k] = a[top][i];
      //  cout<<temp[k]<<" ";
        ++k;
    }
    ++top;

    if(top > down || left > right) 
        break;

    for(int i = top; i <= down ; ++i)
    {
        temp[k]=a[i][right];
       // cout<<temp[k]<<" ";
        ++k;
    }
    --right;
    if(top > down || left > right) 
        break;

    for(int i = right; i >= left ; --i)
    {
        temp[k] = a[down][i];
      //  cout<<temp[k]<<" ";
        ++k;
    }
    --down;

    if(top > down || left > right) 
        break;

    for(int i = down; i >= top ; --i)
    {   
        temp[k] = a[i][left];
       // cout<<temp[k]<<" ";
        ++k;
    }

    ++left;
    if(top > down || left > right) 
        break;

    //________________________________\\

    size = k;
    k=0;
   // cout<<size<<endl;
    for(int i = tleft; i <= tright ; ++i)
    {
        b[ttop][i] = temp[(k + (r%size))%size];
     //   cout<<(k + (r%size))%size<<" ";
     //   int index = (k + (r%size))%size;
       // cout<<index;
        ++k;
    }
    ++ttop;

    for(int i = ttop; i <= tdown ; ++i)
    {
        b[i][tright]=temp[(k + (r%size))%size];
        ++k;
    }
    --tright;

    for(int i = tright; i >= tleft ; --i)
    {
        b[tdown][i] = temp[(k + (r%size))%size];
        ++k;
    }
    --tdown;

    for(int i = tdown; i >= ttop ; --i)
    {   
        b[i][tleft] = temp[(k + (r%size))%size];
        ++k;
    }

    ++tleft;
}

size=k;
k=0;
if(top != ttop){
    for(int i = tleft; i <= tright ; ++i)
    {
        b[ttop][i] = temp[(k + (r%size))%size];
        ++k;
    }
    ++ttop;
}
if(right!=tright){
    for(int i = ttop; i <= tdown ; ++i)
    {
        b[i][tright]=temp[(k + (r%size))%size];
        ++k;
    }
    --tright;
}
if(down!=tdown){
    for(int i = tright; i >= tleft ; --i)
    {
        b[tdown][i] = temp[(k + (r%size))%size];
        ++k;
    }
    --tdown;
}
if(left!=tleft){
    for(int i = tdown; i >= ttop ; --i)
    {   
        b[i][tleft] = temp[(k + (r%size))%size];
        ++k;
    }

    ++tleft;
}
for(int i = 0 ; i < m ;++i){
    for(int j = 0 ; j < n ;++j)
        cout<<b[i][j]<<" ";
    cout<<endl;
}

return 0;
}

1
对于特定的元素和旋转数,称其为_R(i,j),你能找出原始元素(i,j)_在哪里吗?如果可以,你就有一个简单的变换要应用。提示:考虑R的特殊值,如_2*(M+N)_。 - Useless
4个回答

4
你需要分解这个问题(让我想起 gg 和 fb 的面试题):
1. 首先解决将序列旋转一个位置的问题。 2. 然后解决将序列旋转 N 次的问题。 3. 将每个“环”或“圆环”建模为一个数组。您可以实际上需要将其存储在单独的数据中,也可以不需要。 4. 迭代每个环并应用旋转算法。
假设有一个长度为 L 的数组需要旋转 R 次。请注意,如果 R 是 L 的倍数,则数组将不会改变。同时,旋转 x 次向右等同于向左旋转 L - x 次(反之亦然)。
因此,您可以首先设计一种能够向左或向右旋转一个位置的算法。
将向左旋转 R 次的问题简化为向左旋转 R%L 次。
如果您要进一步减少问题的复杂度,可以将向左旋转 R modulo L 的问题简化为向左旋转 R modulo L 或向右旋转 L - R modulo L。这意味着如果您有 100 个元素,并且必须向左旋转 99 次,则最好向右旋转 1 次并完成它。
因此,复杂度将是 O(圈数 x 圆环长度 x 单次旋转成本)。
使用原地数组意味着 O(min(N,m) x (N x M)^2)。
如果您使用双向链表作为临时存储,单个旋转序列是通过删除前面并将其放在尾部(或反之亦然以向右旋转)来完成的。因此,您可以首先将所有数据复制到链表中。运行单个旋转算法 R modulo L 次,将链表复制回环位置,并继续下一个右侧,直到处理完所有圆环。
将环数据复制到列表的成本是 O(L),其中 L <= N*M。
单次旋转成本为 O(1)。
所有旋转 R modulo L 成本为 O(L)。
重复在所有 min(N,m) 个圆环上。
使用备用双向链表的复杂度为 O(min(N,m) x (N x M))。

你可以使用另一个具有L个元素的数组来代替列表,并且通过一些移位来复制元素。在实践中应该会更快。 - Sopel

1
我会从一个简化的假设开始:M小于或等于N。因此,您保证有偶数行。(如果M > N怎么办?那就转置矩阵,执行算法,然后再次转置矩阵。)
由于您有偶数行,可以轻松找到矩阵中每个循环的角落。最外层循环有这些角落:
a1,1 → aM,1 → aM,N → a1,N 为了找到下一个循环,将每个角落向内移动,这意味着适当地增加或减少每个角落的索引。
知道角落的顺序允许您迭代每个循环并将值存储在一维向量中。在每个这样的向量a中,从索引R%a.size()开始,并递增索引a.size()- 1次以迭代循环的旋转元素。将每个元素a [i%a.size()]复制回循环。
请注意,我们实际上并没有旋转向量。我们通过从偏移索引开始将元素复制回矩阵来实现旋转。因此,算法的总运行时间为O(MN),这是最优的,因为仅读取输入矩阵就需要O(MN)的成本。

0
我会将这个问题视为将矩阵分割成子矩阵的问题。你可以编写一个函数,每次调用时将矩阵(和子矩阵)的外部行和列向外移动一位。注意适当处理矩阵的四个角落。
针对如何移动列,请参考this编辑(更详细): 将每个矩阵圈读入为一个向量,使用std::rotate函数进行 R % length.vector 次旋转,然后写回。最多执行150次操作。

由于 R = 10^9,这将导致大量旋转。会导致超时。 - Kartik Sharma
抱歉,我之前忽略了这个问题。但是,如果你知道每个“圆”的长度,你可以对每个圆执行 R 模长操作,从而知道需要移动多少。这样不会导致超时。 - ChrisF

0
每个元素都根据四个公式之一独特地移动,添加五个已知大小的运动(我会留下大小计算,因为你想自己解决):
formula (one of these four):

left + down + right + up + left
down + right + up + left + down
right + up + left + down + right
up + left + down + right + up

由于矩阵的最小边长是偶数,我们知道没有元素会保持在原位。经过 R 次旋转后,该元素已经绕了 floor(R/formula) 圈,但仍需要进行 extra = R % formula 次移位。一旦你知道了 extra,就可以简单地计算出该元素的适当位置。


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