[算法系列] 多维数组–记几道经典的多维数组习题
1. 顺时针打印二维数组
传入一个二维数组
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
顺时针打印出: 1 2 3 4 8 12 16 15 14 13 9 5 6 7 8 12 11 10
分析: 有如下二维数组, 那么它具体打印顺序是像右面这样的:
按照 1…5…25… 顺序逐渐从外层向内层螺旋遍历.实际上,我们可以将其拆分成为有规律遍历部分:
红线遍历最外层, 当到达6后, 蓝线从7继续遍历, 不难看出蓝线的运动模式和红线一模一样 , 唯一不同之处在于: 运动的数组范围比之前小了一圈 . 因此, 我们可以先描述红线运动轨迹; 然后将整体范围缩小一圈, 再描述蓝线; 然后整体再缩小一圈…
我们用 r, c 来表示当前要打印的元素的横坐标和纵坐标.
如何表示数组的范围呢?? (第一圈为 n ,第二圈整体n-1) 另外在描述 运动模式 (其实这个运动模式就是遇到定义的范围边界就倒拐)
我们可以用两个点(左上, 右下)四个坐标值来规整遍历的范围:
left_up_h = 0 #左上点横坐标
left_up_v = 0 #左上点纵坐标
right_down_h = len(matrix) - 1 #右下点横坐标
right_down_v = len(matrix[0]) - 1 #右下点纵坐标
每轮遍历起始时指针都在左上:
r = left_up_h #用于指向当前打印元素所在横坐标
c = left_up_v #用于指向当前打印元素所在纵坐标
上下左右四条边的运行轨迹:
#上面一条边
while c <= right_down_v:
print(matrix[r][c] , end=" ")
c += 1
#c 现在大于 right_down_v, 恢复
c = right_down_v
r += 1 #向下一行
#右侧一条边
while r <= right_down_h:
print(matrix[r][c], end=" ")
r +=1
r = right_down_h
c -= 1 #向左一列
#下面一条边
while c >= left_up_v:
print(matrix[r][c], end= " ")
c -= 1
c = left_up_v
r-= 1
#左边一条边
while r > left_up_h:
print(matrix[r][c] , end=" ")
r -= 1
如上, 一圈已经打印完毕. 本轮结束时 r,c 已经在下一轮的起始位置了, 参考上图的 7 的位置. 下面要做的就是将 四个边界值进行调整:
#左上点横纵坐标均加1
left_up_h += 1
left_up_v += 1
#右上点横纵坐标均减1
right_down_h -= 1
right_down_v -= 1
现在考虑循环的终止条件 : 当左上的点的横坐标比右下的横坐标还大, 纵坐标比右下点的纵坐标还大时即可停止.
while left_up_ h <= right_down_h and left_up_v <= right_down_v: #这是外层循环
因此整体代码如下:
def print_arr_clockwise(matrix ):
left_up_h = 0 # 左上点横坐标
left_up_v = 0 # 左上点纵坐标
right_down_h = len(matrix) - 1 # 右下点横坐标
right_down_v = len(matrix[0]) - 1 # 右下点纵坐标
while left_up_h <= right_down_h and left_up_v <= right_down_v: # 这是外层循环
r = left_up_h # 用于指向当前打印元素所在横坐标
c = left_up_v # 用于指向当前打印元素所在纵坐标
# 上面一条边
while c <= right_down_v:
print(matrix[r][c], end=" ")
c += 1
# c 现在大于 right_down_v, 恢复
c = right_down_v
r += 1 # 向下一行
# 右侧一条边
while r <= right_down_h:
print(matrix[r][c], end=" ")
r += 1
r = right_down_h
c -= 1 # 向左一列
# 下面一条边
while c >= left_up_v:
print(matrix[r][c], end=" ")
c -= 1
c = left_up_v
r-= 1
# 左边一条边
while r > left_up_h:
print(matrix[r][c], end=" ")
r -= 1
# 左上点横纵坐标均加1
left_up_h += 1
left_up_v += 1
# 右上点横纵坐标均减1
right_down_h -= 1
right_down_v -= 1
matrix = [
[1, 2, 3, 4,100],
[5, 6, 7, 8,101],
[9, 10, 11, 12,102],
[13, 14, 15, 16,103],
[108,107,106,105,104]
]
print_arr_clockwise(matrix)
2. 清空0所在行列
输入一个二维数组, 将含有数字0的所在行和列全部清0
如输入一个matrix :
matrix = [
[1,2,0,4,5],
[6,0,7,8,9],
[3,4,2,1,1],
[9,7,8,5,0],
[4,0,6,4,2]
]
输出:
0 0 0 0 0
0 0 0 0 0
3 0 0 1 0
0 0 0 0 0
0 0 0 0 0
'''
输入一个二维数组, 将含有数字0的所在行和所在列全清0
思路:
1.创建一个和原数组matrix同样大小的全为1的数组matrix_flag
2.遍历原数组, 出现0则在matrix_flag中将同样位置所在行和列置0
3.重新遍历matrix以及matrix_flag, 要是matrix_flag[i][j]=0,则将matrix[i][j] 置为0
为啥要按照上面思路呢?
因为直接在原数组上将行列改成0可会使得本来就是0的位置变得不清晰(不知道是否本身就是0)
'''
def clean_to_0_in_matrix(matrix):
row = len(matrix)
col = len(matrix[0])
print_matrix(matrix) # 就是按照行列打印数组
#1. 标记数组的定义
matrix_flag = [[ 1 for i in range(0, col )] for j in range(0, row)] #和原数组同大小,全置为1
#2. 标记数组按照要求置0
for i in range(0,row):
for j in range(0 , col):
if matrix[i][j] == 0:
# 将matrix_copy 中i行和j列全置为0
alter_copy_matrix_to_0(matrix_flag , i , j)
#3. 根据现在的标记数组再回过头来调整原数组的值
for i in range(0,row):
for j in range(0 , col):
if matrix_flag[i][j] == 0:
matrix[i][j] = 0
def alter_copy_matrix_to_0(matrix , h , v):
'''
将matrix的 h行全变为0,将v列全变成0
'''
for i in range(0 , len(matrix)):
for j in range(0 , len(matrix[0])):
if i == h or j == v:
matrix[i][j] = 0
# 打印数组函数
def print_matrix(matrix):
for row in matrix:
for e in row:
print(e, end ="\t")
print("\n")
3. z字型打印数组
输入一个二维数组, 按照z型打印. 如下:
按照红线从1进行打印
输出: 1 2 6 11 7 3 4 8 12 13 9 5 10 15
我们仔细分析条斜线上打印的顺序. 有
左下->右上:1
右上->左下:2 6
左下->右上:11 7 3
右上->左下:4 8 12
左下->右上:13 9 5
右上->左下:10 14
左下->右上:15
简而言之, 像一个缝纫机似的不停地来回斜着打印数组. 问题关键在于何时转向, 比如1->2. 3->4 , 6->11等
依然用(r,c)表示当前打印的元素的下标, 另外用一个boolean类型的变量l2r 控制方向,为true时左下到右上, 为false时右上到左下
#初始
r = 0
c = 0
l2r = True #为true表示左下到右上
现在来描述l2r为True时:
打印当前元素
如果 是第一行 and 未到最右列: //只能向右
右移一位
改变方向
否则如果 不是第一行 and 是最后一列: //只能向下
下移一位
改变方向
否则 //表是正常途中
r--
c++
当l2r为False时: 所有走法相反
打印当前元素
如果是第一列 and 未到最后一行: //只能向下
下移一位
改变方向
否则如果是最后一行: //只能向右
右移一位
改变方向
否则 //表示正常途中
r++
c--
代码:
def z_print(matrix):
m = len(matrix)
n = len(matrix[0])
r = 0
c = 0
l2r = True
while r < m and c < n:
if l2r == True:
print(matrix[r][c] ,end=" ")
if r == 0 and c < n - 1: # 第一排但不是最后一列: 向右
c += 1
l2r = not l2r
continue
elif c == n - 1: #到达最后一列: 向下
r += 1
l2r = not l2r
continue
else: #正常情况, 该咋走咋走
r -= 1
c += 1
else:
print(matrix[r][c] ,end= " ")
if r == m - 1 : #最后一排: 向右
c += 1
l2r = not l2r
continue
elif c == 0 and r < m - 1: #第一列但不是最后一排: 向下
r += 1
l2r = not l2r
continue #正常
else:
r += 1
c -= 1
3. 边界为1的最大子方阵
给定一个N*N的矩阵matrix, 在此矩阵中, 只有0和1两种值, 返回边框全是1的最大正方形的边长长度
如图给定的矩阵中, 边框全是1的最大正方形的大小是4*4 , 返回4
[法1] 从边框长度(设为l)为N开始, 依次从(0,0)遍历(遍历变量为r,c)其上下左右是否存在0 , 若存在, 跳出循环, 在r + l <N时沿着走一圈,
[法2] 上述方法相对麻烦, 复杂度颇高 . 现在考虑另一种方法: 首先:在每个位置处记录当前位置下方和右方有多少个1 – 打表法, dp中会常用
具体如下:
创建一个5 * 5 * 2的空数组record, 其相应位置的值有两种情况:
- 若原位置值为0 , 则保持0,0
- 若不为零, 逗号前面的值为其下方逗号前的值加1, 逗号后面的值为右方逗号后的值加1
比如4B上的值为1,则在record相应位置为2,4 . 表名包括自己,在自己下方有2个1, 右方有4个1.
现在依次遍历record, 若此处(设坐标 r,c)值为0,0 则跳过. 否则记录下逗号前或后面较小的那个数字即为 n:
- 考察此处(r,c)下方元素:
从record [ r + n -1] [c]
开始考察,
如果该处逗号后的值小于n, 则 n - - ,继续循环
否则break
-
考察此处(r,c)右方方元素:
从
record[r][r + n - 1]
开始考察 如果该处逗号前的值小于n, 则 n – 继续循环
否则break
此时得到的n即为从该处画出的最大正方形边长, 如果大于res , 则更新res = n
代码如下:
def find_max_matrix(matrix):
N = len(matrix)
#创建record并填入数组
record =[[[0 for k in range(0,2)] for j in range(0, N )] for i in range(0 , N)]
for i in range (N - 1, -1, -1 ):
for j in range (N - 1 , -1 ,-1):
#如果原位置为0, 则record相应位置为0
if matrix[i][j] == 0:
record[i][j][0] = 0
record[i][j][1] = 0
continue
#如果是最下面一行,逗号前一定为1
elif i == N - 1 and j < N - 1:
record[i][j][0] = 1
record[i][j][1] = record[i][j + 1][1] + 1
#如果是最后一列, 逗号后一定为1
elif j == N - 1 and i < N - 1:
record[i][j][1] = 1
record[i][j][0] = record[i + 1][j][0] + 1
elif i == N - 1 and j == N - 1:
record[i][j][0] = 1
record[i][j][1] = 1
else:
record[i][j][0] = record[i + 1][j][0] + 1
record[i][j][1] = record[i][j + 1][1] + 1
# 现在顺序遍历record 来判断每个位置的最大n ,
res = 0 #res 表示最终结果
n = 0 # 保存每个record[r][c][0]和record[r][c][1]中较小的那个
for r in range (0 , N ):
for c in range(0 , N ):
#对每个record[r][c]的每个位置, 进行如下操作
if record[r][c][0] == 0: # 此位为0 , 跳过
continue
#用小的作为n
if record[r][c][0] > record[r][c][1]:
n = record[r][c][1]
else:
n = record[r][c][0]
#往下判断
for d in range(r + n - 1 , r , - 1):
if record[d][c][1] < n:
n -= 1
continue
else:
break
#往右判断
for e in range(c +n - 1 , c , - 1):
if record[r][e][0] < n:
n -= 1
continue
else:
break
if res < n:
res = n
return res
4. 子数组最大累加和
给定一个数组arr ,返回子数组的最大累加和
例: arr= [1,-2,3,5,-2,6,-1]; 其所有子数组中[3,5,-2,6] 可以累加出最大的和12 , 所以返回12
[法1] 显然此题可以暴力遍历每一个子数组,每轮每加一个值与目前的最大值进行比较. O(n^2)
[法2]从左到右连续累加, 若累加出来的结果为负数, 则舍去, 从下一个值开始从0重新进行累加; 反之将其加上, 继续遍历下一个值.
该法的复杂度为O(n)
def find_subarr_max_sum(arr):
sum = arr[0] #sum 用于存目前子数组的和,初始为arr[0]
max = sum #max用于存放当前的最大值
for j in range(1 , len(arr)):
if sum > 0 : #左子数组的最大和为正,继续向后累加
sum += arr[j]
else:
sum = arr[j] #否则相当于直接从这一位从新开始累加
if sum >max:
max = sum
return max
此题可引出二维形式的
5. 子矩阵最大累加和
给定一个矩阵matrix , 其中的值有正,负,0, 返回子矩阵的最大累加和
例如,
matrix=[
[-1,-1,- 1],
[-1, 2, 2 ],
[-1, -1, -1]
]
其中最大累加和的子矩阵为:2 2; 故返回4
思路:将二维矩阵看成多个一维数组的重叠(累加).
从第1行开始,首先将第1行作为一个数组, 进行一维数组最大子数组累加和的求解.
接着将第1行与第2行对应元素相加, 形成一个新的数组, 再对该数组进行最大子数组累加和的求解,并与之前的比
接着是将1,2,3行全部对应相加, 重复上述过程
然后是第二行作为一个数组求…,第二行与第三行相加作为一个数组求… 每次得到一个新的一维数组都求解其最大子数组和.
该法的复杂度应为n * n(n - 1)/2 为O(n^3)
def find_submatrix_max_sum(matrix):
max = matrix[0][0] # max 存储最大值
#从第一行开始, 每次记得向下加和得到新数组
for i in range(0, len(matrix)): #当前从 i 行开始
for k in range(i , len(matrix)): #当前从i行要加到k行
if i == k: #刚开始第一行, 不用加, 直接求
sum = find_subarr_max_sum(matrix[i])
if sum > max:
max = sum
continue
# 数组累加, 从第i行逐个向下加到k ,求最长子数组
row = i + 1 #row 在i和k之间遍历
arr = matrix[i]
while row <= k:
arr = add_two_arr(arr , matrix[row])
sum = find_subarr_max_sum(arr)
if sum >max:
max = sum
row += 1
return max
def add_two_arr(arr1, arr2):
i = 0
arr = [0] * len(arr1)
while i < len(arr1):
arr[i] = arr1[i]+ arr2[i]
i +=1
return arr