[算法系列] 多维数组--超详细解答!!记几道经典的多维数组习题(顺时针打印矩阵,z字型打印矩阵等)

[算法系列] 多维数组–记几道经典的多维数组习题

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
发布了47 篇原创文章 · 获赞 108 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/Lagrantaylor/article/details/104240452