将矩阵旋转 n 次

Posted

技术标签:

【中文标题】将矩阵旋转 n 次【英文标题】:Rotate a matrix n times 【发布时间】:2015-08-18 11:50:41 【问题描述】:

当我遇到这个问题时,我正在解决 HackerRank 上的问题。

问题陈述

给定一个二维矩阵 a,维数为 MxN 和一个正整数 R。您必须将矩阵旋转 R 次并打印结果矩阵。旋转应该是逆时针方向。

一个4x5矩阵的旋转如下图所示。请注意,在一次旋转中,您只需将元素移动一步(请参阅示例测试以获得更清晰的信息)。

保证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 轻松地旋转一维矩阵。但是我如何将它放回矩阵形式?再次使用循环可能会导致超时。

请不要提供代码,只提供建议。我想自己做。

已创建解决方案:

#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;

【问题讨论】:

对于一个特定的元素和旋转数,叫它R(i,j),你能弄清楚原始元素在哪里(i,j)我> 是?如果是这样,您可以应用一个简单的转换。提示:考虑 R 的特殊值,例如 2*(M+N). 【参考方案1】:

你需要分解这个问题(让我想起一个来自 gg 和 fb 的面试问题):

    首先将序列旋转一个位置 然后求解旋转序列 N 次 将每个“圆”或环建模为一个数组。您可能需要也可能不需要将数据存储在单独的数据中 遍历每个环并应用旋转算法

让我们考虑一个长度为L 的数组需要旋转R 时间的情况。请注意,如果RL 的倍数,则数组将保持不变。 还要注意向右旋转x 次与向左旋转L - x 相同(反之亦然)。

    因此,您可以首先设计一种算法,该算法能够向左或向右旋转一次,精确到一个位置 将R向左旋转次数减少为R modulo L向左旋转问题 如果你想进一步减少将R modulo L向左旋转到向左旋转R modulo L或向右旋转L - R modulo L的问题。这意味着如果您有 100 个元素并且必须向左旋转 99 次,那么您最好向右旋转 1 次并完成它。

所以复杂度将是 O(圈数 x 圈长 x 单次旋转成本)

就地数组意味着O( min(N,m) * (N * M)^2 )

如果您使用双向链表作为临时存储,则通过删除前面并将其放在尾部(反之亦然以向右旋转)来完成单个旋转序列。所以你可以做的是首先将所有数据复制到一个链表中。运行单轮旋转算法R modulo L次,将环位置上的链表复制回来,向右下移动,直到处理完所有环。

复制ring数据到列表是O(L), L &lt;= N*M 单次轮换成本为 O(1) 所有旋转R modulo LO(L) 重复所有min(N,m) rings

使用备用双链表意味着O( min(N,m) * (N * M))的复杂度

【讨论】:

您可以使用带有L 元素的附加数组来代替列表,并通过一些移位复制元素。在实践中应该更快。【参考方案2】:

我将从一个简化的假设开始: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)。

【讨论】:

【参考方案3】:

我会将此视为将矩阵划分为子矩阵的问题。您可能可以编写一个函数,每次调用它时将矩阵(和子矩阵)的外部行和列移动一个。注意适当处理矩阵的四个角。

查看this 以获取如何移动列的建议。

编辑(更详细):

将每个矩阵圆作为向量读取,对其使用 std::rotate R % length.vector 次,然后写回。最多 150 次操作。

【讨论】:

由于 R = 10^9,这将是很多旋转。会导致超时。 抱歉,忽略了这一点。但是,如果您知道每个“圆”的长度,您可以为每个圆做 R 模长度,并知道您将移动多少。这不应该导致超时。【参考方案4】:

每个元素根据四个公式之一唯一地移动,添加五个已知大小的移动(我将不考虑大小计算,因为你想弄清楚):

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 后,只需计算元素的适当位置即可。

【讨论】:

以上是关于将矩阵旋转 n 次的主要内容,如果未能解决你的问题,请参考以下文章

刷题 11/05

将矩阵旋转到位

给定一个 n × n 的二维矩阵表示一个图像, 将图像顺时针旋转 90 度js实现

矩阵旋转(二维数组旋转)

Leecode-48:旋转图像(矩阵顺时针旋转90度)

矩阵的旋转