将矩阵旋转 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
时间的情况。请注意,如果R
是L
的倍数,则数组将保持不变。
还要注意向右旋转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 <= N*M
单次轮换成本为 O(1)
所有旋转R modulo L
是O(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 次的主要内容,如果未能解决你的问题,请参考以下文章