在矩阵中找到最终的方形,就像螺旋一样

时间:2015-11-06 16:02:55

标签: algorithm math language-agnostic dynamic-programming

给定矩阵A x A和一些移动N

走路像螺旋一样:

  1. 尽可能正确,然后
  2. 尽可能
  3. ,然后
  4. 尽可能离开,然后
  5. 尽可能
  6. ,重复直至获得N
  7. 带有示例的图片(A = 8; N = 36

    enter image description here

    在此示例中,最终方格为(4; 7)

    我的问题是:是否可以使用通用公式来解决这个问题?

3 个答案:

答案 0 :(得分:3)

,可以计算出答案。

为此,有助于将问题分成三个部分。

(注意:我从零开始计算以简化数学运算。这意味着您必须将1添加到答案的某些部分。例如,我对{{1}的答案}将是最终的方格A = 8, N = 36,其标签为(3; 6)。)

(另请注意:这个答案与Nyavro's answer非常相似,只是我在这里避免了递归。)

在第一部分中,您计算​​对角线上的标签:

  • 35标有(0; 0)
  • 0标签为(1; 1)。该周期可以平均分为四个部分(带有您的标签:4*(A-1)1..78..1415..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)

这是一个基本的解决方案,可能会进一步推广 - 我只是不知道你需要多少概括。所以你可以得到这个想法。

修改

注意:

  1. 解决方案是基于0的索引
  2. 需要检查是否存在(n> = a * a)

答案 2 :(得分:0)

我将在这里提出一个相对简单的解决方法,它会在O(A ^ 2)时间内生成所有索引,以便以后可以在O(1)中访问任何N.如果A发生变化, ,我们必须再次执行算法,这将再次消耗O(A ^ 2)时间。

我建议您使用这样的结构来存储索引以访问矩阵:

Coordinate[] indices = new Coordinate[A*A]

Coordinate只是一对int

然后,您可以使用一些循环填充indices数组: (此实现使用基于1的数组访问。如果这是一个问题,请相应地更正包含isentinelcurrentDirection的表达式。)

    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的效果相同。我在我的版本中没有完成这一切,因为我认为只用一个布尔值就可以更容易理解。

希望这有帮助!