给定矩阵A x A
和一些移动N
。
走路像螺旋一样:
答案 0 :(得分:3)
是,可以计算出答案。
为此,有助于将问题分成三个部分。
(注意:我从零开始计算以简化数学运算。这意味着您必须将1
添加到答案的某些部分。例如,我对{{1}的答案}将是最终的方格A = 8, N = 36
,其标签为(3; 6)
。)
(另请注意:这个答案与Nyavro's answer非常相似,只是我在这里避免了递归。)
在第一部分中,您计算对角线上的标签:
35
标有(0; 0)
。0
标签为(1; 1)
。该周期可以平均分为四个部分(带有您的标签:4*(A-1)
,1..7
,8..14
,15..21
)。22..27
标签为(2; 2)
。在4*(A-1) + 4*(A-3)
矩阵周围进行一个周期后,您的下一个周期将围绕A x A
矩阵。等等。现在有很多方法可以找出(A - 2) x (A - 2)
的一般规则((K; K)
时)。我会选择最容易展示的那个:
0 < K < A/2
(注意:在4*(A-1) + 4*(A-3) + 4*(A-5) + ... + 4*(A-(2*K-1)) =
4*A*K - 4*(1 + 3 + 5 + ... + (2*K-1)) =
4*A*K - 4*(K + (0 + 2 + 4 + ... + (2*K-2))) =
4*A*K - 4*(K + 2*(0 + 1 + 2 + ... + (K-1))) =
4*A*K - 4*(K + 2*(K*(K-1)/2)) =
4*A*K - 4*(K + K*(K-1)) =
4*A*K - 4*(K + K*K - K) =
4*A*K - 4*K*K =
4*(A-K)*K
和4*(A-K)*K = 28
时检查A = 8
。将其与示例中K = 1
的标签进行比较。) < / p>
现在我们知道对角线上有哪些标签,我们可以计算出我们必须从(2; 2)
矩阵中删除多少层(比如K
),以便最终的正方形位于边缘。如果我们这样做,那么回答我们的问题
我在
A x A
矩阵中执行(X; Y)
步时的坐标N
是什么?
可以通过计算此A x A
和代替来解决问题
我在
K
矩阵中执行(X - K; Y - K)
步时的坐标N - 4*(A-K)*K
是什么?
为此,我们应该找到(A - 2*K) x (A - 2*K)
和K
这样的最大整数K < A/2
。
对此的解决方案是4*(A-K)*K <= N
。
剩下的就是找出沿K = floor(A/2 - sqrt(A*A-N)/2)
矩阵边缘N
的正方形的坐标:
A x A
,坐标为0*E <= N < 1*E
; (0; N)
,坐标为1*E <= N < 2*E
; (N - E; E)
,坐标为2*E <= N < 3*E
;和(E; 3*E - N)
,坐标为3*E <= N < 4*E
。此处(4*E - N; 0)
。
总而言之,这是一个天真的(E = A - 1
因为浮动不准确而给layerNumber
的大值提供了错误答案)Haskell实现了这个答案:
a
答案 1 :(得分:1)
以下是可能的解决方案:
f a n | n < (a-1)*1 = (0, n)
| n < (a-1)*2 = (n-(a-1), a-1)
| n < (a-1)*3 = (a-1, 3*(a-1)-n)
| n < (a-1)*4 = (4*(a-1)-n, 0)
| otherwise = add (1,1) (f (a-2) (n - 4*(a-1))) where
add (x1, y1) (x2, y2) = (x1+x2, y1+y2)
这是一个基本的解决方案,可能会进一步推广 - 我只是不知道你需要多少概括。所以你可以得到这个想法。
修改
注意:
答案 2 :(得分:0)
我将在这里提出一个相对简单的解决方法,它会在O(A ^ 2)时间内生成所有索引,以便以后可以在O(1)中访问任何N.如果A发生变化, ,我们必须再次执行算法,这将再次消耗O(A ^ 2)时间。
我建议您使用这样的结构来存储索引以访问矩阵:
Coordinate[] indices = new Coordinate[A*A]
Coordinate
只是一对int
。
然后,您可以使用一些循环填充indices
数组:
(此实现使用基于1的数组访问。如果这是一个问题,请相应地更正包含i
,sentinel
和currentDirection
的表达式。)
Coordinate[] directions = { {1, 0}, {0, 1}, {-1, 0}, {0, -1} };
Coordinate c = new Coordinate(1, 1);
int currentDirection = 1;
int i = 1;
int sentinel = A;
int sentinelIncrement = A - 1;
boolean sentinelToggle = false;
while(i <= A * A) {
indices[i] = c;
if (i >= sentinel) {
if (sentinelToggle) {
sentinelIncrement -= 1;
}
sentinel += sentinelIncrement;
sentinelToggle = !sentinelToggle;
currentDirection = currentDirection mod 4 + 1;
}
c += directions[currentDirection];
i++;
}
好的,关于解释:我使用一个名为sentinel
的变量来跟踪我需要切换方向的位置(通过循环遍历数组directions
简单地切换方向) 。
sentinel
的值以这样的方式递增,即它始终具有螺旋中的拐角索引。在你的例子中,哨兵将采用值8,15,22,28,34,39 ......等等。
请注意&#34; sentinel&#34;的索引。两次增加7(8,15 = 8 + 7,22 = 15 + 7),然后增加6(28 = 22 + 6,34 = 28 + 6),然后增加5,依此类推。在我的while循环中,我使用了布尔sentinelToggle
。每次我们到达螺旋的一角(这正好是if i == sentinel
,这是if条件的来源)我们将标记增加sentinelIncrement
和更改方向我们正在前进。如果sentinel
增加了两次相同的值,则if条件if (sentinelToggle)
将为真,因此sentinelIncrement
减1。我们必须减少sentinelIncrement
,因为随着我们的继续,我们的螺旋变得越来越小。
只要i&lt; = A * A,即只要我们的数组indices
仍然有零条目,就会继续这样做。
请注意,这并没有给出关于N的螺旋坐标的闭合公式(也就是O(1));相反,生成所有N的索引,这些索引占用O(A ^ 2)时间,然后通过简单地调用indices[N]
来保证在O(1)中的访问。
O(n ^ 2)希望不应该受到太大的伤害,因为我假设您还需要在某个点填充矩阵,这也需要O(n ^ 2)。
如果效率是一个问题,请考虑摆脱sentinelToggle
,这样就不会弄乱分支预测。相反,每次满足while条件时递减sentinelIncrement
。要获得sentinel
值的相同效果,只需在(A - 1)* 2处启动sentinelIncrement
,每次满足if条件时,执行:
sentinel += sentinelIncrement / 2
整数除法与每秒仅减少sentinelIncrement
的效果相同。我在我的版本中没有完成这一切,因为我认为只用一个布尔值就可以更容易理解。
希望这有帮助!