[Algo] Matrix Rotation 解题思路及过程

题目的原始地址:https://www.hackerrank.com/challenges/matrix-rotation-algo

说一下解题过程中的思路:

1. 一个矩阵的旋转,实际上是分成几层的环在旋转,如何确定有几层环呢?请见代码段:(其中注释所说的约束条件就是min(m, n) % 2 == 0

# 根据(m,n)两者中的最小值, 可以推算出矩阵可以分成几个环进行旋转
# 推算方法: 获得(m,n)的最小值, 使用这个最小值整除2
# 原理: 每个环都是上下各向内退一位, 左右各向内退一位, 所以整除2即可(有一个约束条件见题目)
circles = min(m, n) // 2

2. 然后就是对于一个环我们怎么来实现旋转。题目要求是逆时针旋转,可以考虑将一个环分解为上、下、左、右四条边,对于上边是右侧的值赋给左侧,对于右边是下侧的值赋给上侧,对于下边是左侧的值赋给右侧,对于左边是上侧的值赋给下侧:(四个参数值分别表示某一个环左上角和右下角的坐标(x1, y1), (x2, y2)

def rotate(px1, px2, py1, py2):
    temp = arr[px1][py1]
    # 上方各个值向左赋值
    for y in range(py1, py2):
        arr[px1][y] = arr[px1][y+1]
    # 右方各个值向上赋值
    for x in range(px1, px2):
        arr[x][py2] = arr[x+1][py2]
    # 下方各个值向右赋值
    for y in range(py2, py1, -1):
        arr[px2][y] = arr[px2][y-1]
    # 左方各个值向下赋值
    for x in range(px2, px1, -1):
        arr[x][py1] = arr[x-1][py1]
    arr[px1+1][py1] = temp

3. 接下来我很自然的进入一个常规的误区:题目要求做R次旋转,那么就循环R次好了,然后在这个循环内再嵌套一个循环对每一层环进行旋转,见下面的代码段:

for c in range(r):
    for circle in range(circles):
        # 计算每一个环的起止位置
        # 实际上可以理解为获取每一个环对应矩阵的左上角坐标(x1,y1)和右下角坐标(x2,y2)
        x1 = 0 + circle
        x2 = m - 1 - circle
        y1 = 0 + circle
        y2 = n - 1 - circle

        rotate(x1, x2, y1, y2)

从算法实现的角度来讲,这个思路可以获得正确的结果,但是再仔细看看题目中的约束条件:(M-矩阵行数,N-矩阵列数,R-旋转次数,aij-矩阵坐标(i, j)上的值)

2 <= MN <= 300 
1 <= R <= 109 
1 <= aij <= 108, where i ∈ [1..M] & j ∈ [1..N]

可以看到矩阵最大可以包含90,000个值,旋转次数可以高达10的9次方,这个时候回头看看上面的实现。。。时间消耗巨大啊!

4. 优化的考虑:假设旋转次数足够大,那么可能会出现环从起点旋转回起点然后继续旋转的情况,而且越靠内的环旋转回起点的次数越多,如果排除掉这些旋转的话将能够节省非常多次的“无效”旋转。排除的过程很简单,假设一个环上有P个值,那么经过P次旋转后这个环回到初始位置:

# 核心点: 计算每个环的旋转次数
# 如果严格按照给定的r来进行旋转操作, 有可能会重复旋转多次, 消耗不必要的处理时间
# 所以需要计算每一个环上的数字个数, 通过r和它进行取余来获得需要旋转的真实次数, 消除多余的旋转
# 矩阵左上角坐标从(0,0)开始, 右下角坐标为(x2,y2)
# 最外层的环上的数字个数可以很容易的看出来: 横向个数为y2的值, 纵向个数即为x2的值, 然后乘以2即可
# 向内各个环上的数字个数同理可以计算, 只是由于每个环左上角的坐标为(1,1),(2,2)......, 所以要先减去两倍的circle值
    rr = r % ((x2+y2-2*circle)*2)

5. 提交测试,12个测试案例全部通过,且耗时满足要求。

 

最后附上完整的实现代码:(本来想附上一个比较复杂的测试案例,结果文本太多贴不上来。。。)

 1 def rotate(px1, px2, py1, py2):
 2     temp = arr[px1][py1]
 3     # 上方各个值向左赋值
 4     for y in range(py1, py2):
 5         arr[px1][y] = arr[px1][y+1]
 6     # 右方各个值向上赋值
 7     for x in range(px1, px2):
 8         arr[x][py2] = arr[x+1][py2]
 9     # 下方各个值向右赋值
10     for y in range(py2, py1, -1):
11         arr[px2][y] = arr[px2][y-1]
12     # 左方各个值向下赋值
13     for x in range(px2, px1, -1):
14         arr[x][py1] = arr[x-1][py1]
15     arr[px1+1][py1] = temp
16 
17 m, n, r = input().strip().split(" ")
18 m, n, r = [int(m), int(n), int(r)]
19 
20 arr = []
21 for i in range(m):
22     arr_row = [int(arr_row_temp) for arr_row_temp in input().strip().split(" ")]
23     arr.append(arr_row)
24 
25 # 根据(m,n)两者中的最小值, 可以推算出矩阵可以分成几个环进行旋转
26 # 推算方法: 获得(m,n)的最小值, 使用这个最小值整除2
27 # 原理: 每个环都是上下各向内退一位, 左右各向内退一位, 所以整除2即可(有一个约束条件见题目)
28 circles = min(m, n) // 2
29 for circle in range(circles):
30     # 计算每一个环的起止位置
31     # 实际上可以理解为获取每一个环对应矩阵的左上角坐标(x1,y1)和右下角坐标(x2,y2)
32     x1 = 0 + circle
33     x2 = m - 1 - circle
34     y1 = 0 + circle
35     y2 = n - 1 - circle
36 
37     # 核心点: 计算每个环的旋转次数
38     # 如果严格按照给定的r来进行旋转操作, 有可能会重复旋转多次, 消耗不必要的处理时间
39     # 所以需要计算每一个环上的数字个数, 通过r和它进行取余来获得需要旋转的真实次数, 消除多余的旋转
40     # 矩阵左上角坐标从(0,0)开始, 右下角坐标为(x2,y2)
41     # 最外层的环上的数字个数可以很容易的看出来: 横向个数为y2的值, 纵向个数即为x2的值, 然后乘以2即可
42     # 向内各个环上的数字个数同理可以计算, 只是由于每个环左上角的坐标为(1,1),(2,2)......, 所以要先减去两倍的circle值
43     rr = r % ((x2+y2-2*circle)*2)
44     for t in range(rr):
45         rotate(x1, x2, y1, y2)
46 
47 for l in range(m):
48     s = map(str, arr[l])
49     print(" ".join(s))

 

 

 

posted @ 2016-06-28 21:46  阿普陀  阅读(752)  评论(0编辑  收藏  举报