[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 <= M, N <= 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))