给定一个 m ∗ n m*n m∗n的矩阵,求最大的子矩阵和。
最大子序列和
在开始求解最大子矩阵和之前,先来看最大子序列和。
使用动态规划求解最大子序列和并不难,其最关键的状态转移方程比较容易理解:
r
e
s
u
l
t
[
i
n
d
e
x
]
=
{
r
e
s
u
l
t
[
i
n
d
e
x
−
1
]
+
a
r
r
a
y
[
i
n
d
e
x
]
r
e
s
u
l
t
[
i
n
d
e
x
−
1
]
>
0
a
r
r
a
y
[
i
n
d
e
x
]
r
e
s
u
l
t
[
i
n
d
e
x
−
1
]
≤
0
result[index] = \begin{cases} result[index - 1] + array[index] & result[index-1] > 0 \\ array[index] & result[index-1] \leq 0 \end{cases}
result[index]={result[index−1]+array[index]array[index]result[index−1]>0result[index−1]≤0 简单概括思路:如果后一位数与前子序列和相加反而会使自己变得更小,则后一位数作为新子序列的第一位数。
最大子矩阵和
思路分析
有了最大子序列和作为铺垫,最大子矩阵和的求解思路就比较明确了。我们应该想办法将多维矩阵压缩为一维数组,然后用最大子序列和求解即可。
稍作思考,压缩矩阵其实并不难,将所有子矩阵的每列元素相加,其和作为一维数组的元素即可将子问题转化为最大子序列求和。
下面的问题是如何获取所有子矩阵。没有什么好的办法,只能暴力遍历。
获取子矩阵后对子矩阵中的列进行求和。为了使提升求和效率,避免重复计算,首先将所有列中的元素进行累加并记录。设子矩阵的第一行为top_row
,最后一行为bottom_row,
对子矩阵中的列进行求和时,求bottom_row
处与top_row - 1
处的累加结果差即可。即:
m
a
t
r
i
x
_
c
o
l
_
s
u
m
[
r
o
w
]
[
c
o
l
]
=
m
a
t
r
i
x
[
0
]
[
c
o
l
]
+
m
a
t
r
i
x
[
1
]
[
c
o
l
]
+
.
.
.
+
m
a
t
r
i
x
[
m
a
t
r
i
x
_
r
o
w
]
[
c
o
l
]
matrix\_col\_sum[row][col]=matrix[0][col]+matrix[1][col]+...+matrix[matrix\_row][col]
matrix_col_sum[row][col]=matrix[0][col]+matrix[1][col]+...+matrix[matrix_row][col]
s
u
b
_
m
a
t
r
i
x
_
c
o
l
_
s
u
m
[
c
o
l
]
=
m
a
t
r
i
x
_
c
o
l
_
s
u
m
[
b
o
t
t
o
m
_
r
o
w
]
[
c
o
l
]
−
m
a
t
r
i
x
_
c
o
l
_
s
u
m
[
t
o
p
_
r
o
w
−
1
]
[
c
o
l
]
sub\_matrix\_col\_sum[col] =matrix\_col\_sum[bottom\_row][col]-matrix\_col\_sum[top\_row-1][col]
sub_matrix_col_sum[col]=matrix_col_sum[bottom_row][col]−matrix_col_sum[top_row−1][col] 最后我们只需注意各种各样的边界条件与状态即可。
输出所有和最大的子矩阵
第一步:确定一个子矩阵需要哪些要素:子矩阵的上边界,下边界,左边界,右边界。
第二步:是否已知所有要素。在遍历所有子矩阵时可以确定子矩阵的上边界与下边界,在进行动态规划时可以确定右边界。由此,左边界未知。
第三步:如何获取左边界。左边界为子序列的起点,因此我们需要在状态转移时记录起点。
第四部:每次的最优解得到更新时,重新定义解集;若第二次出现出现最优解,则将新的子矩阵加入解集。对于元素全为正或全为负的矩阵,最优子矩阵是唯一的,不予讨论;对于元素即有正也有负的的矩阵,我们此时得到的所有子矩阵包含了母矩阵中最“正”的部分(正数的绝对值相对于其它部分显著大于负数。若不理解,先往后看),并不完整。接下来,我们需要拓展解集中的解。以下讨论均以元素即有正也有负的的矩阵为例,且矩阵已经被压缩为一维,转化为最大子序列和问题。
比较容易想到这样一种情况:n个0 + 已知最优子序列
。显然,最优子序列的首尾添加n个0
仍为最优子矩阵。进一步拓展,最优子序列首尾添加和为0
的任意子序列仍为最优子序列。现在,回头来看我们在哪里漏掉了这些。
回到状态转移方程,只有当前子序列的和严格大于0
时我们才会继续延长子序列,否则会开启新的子序列。因此那些和为0的子序列被抛弃掉了。那如果把严格大于0
改为不小于0
不就好了吗?并不是,这就像正则表达式里的贪婪获取与非贪婪获取。当前的状态转移方程会获取尽可能短的最优子序列,因此在首尾会可能漏掉一些和为0的子序列;若改为不小于0
,则会获取尽可能长的最优子序列,在长最优子序列的内部可能存在短最优子序列。
想清楚了这一点,剩下的问题就比较简单了。从已知最优子序列的头部和尾部分别寻找和为0
的最优子序列,分别拼接到头部与尾部即可。
那么问题又来了,我们做到不重不漏了吗?
实际上,我们的最优判断策略已经决定了最短最优子序列的右侧拓展所得最优子序列已存在于解集中。假设右侧存在和为0
的子序列可以添加到已知的最优子序列中,那么根据状态转移方程,必然存在一个紧跟最优子序列的负数
a
a
a满足
a
+
s
u
m
(
k
n
o
w
n
_
b
e
s
t
_
s
u
b
)
<
0
a+sum(known\_best\_sub)<0
a+sum(known_best_sub)<0,否则最优子序列不会被隔断;根据假设,紧跟负数
a
a
a之后必然存在一个子序列满足
s
u
m
(
r
i
g
h
t
_
s
u
b
)
+
a
=
0
sum(right\_sub)+a=0
sum(right_sub)+a=0。联立两式可得,
s
u
m
(
r
i
g
h
t
_
s
u
b
)
>
s
u
m
(
k
n
o
w
n
_
b
e
s
t
_
s
u
b
)
sum(right\_sub)>sum(known\_best\_sub)
sum(right_sub)>sum(known_best_sub)。即,已知的最优子序列不是最优。
综上所述,在使用动态规划得到最优子序列后,在每一个最优子序列左方寻找和为0
的拓展子序列并拼接,即可找到所有满足条件的最优子序列。
若有任何指正之处或交流心得体会请在评论区留言。若喜欢这篇文章记得点个赞哦!
from copy import deepcopy
class MaxSubMatrix(object):
def __init__(self, matrix):
self.matrix = deepcopy(matrix)
self.rows = len(matrix)
self.cols = len(matrix[0])
self.col_sum = deepcopy(matrix)
self.max_sum = - float('inf')
self.target_sub_matrix_index = []
for row in range(1, self.rows):
for col in range(self.cols):
self.col_sum[row][col] += self.col_sum[row - 1][col]
for top_row in range(self.rows):
for bottom_row in range(top_row, self.rows):
result = [0 for _ in range(self.cols)]
col_start = 0
for col in range(0, self.cols):
if top_row == 0:
current_col_sum = self.col_sum[bottom_row][col]
else:
current_col_sum = self.col_sum[bottom_row][col] - self.col_sum[top_row - 1][col]
if col == 0:
result[col] = current_col_sum
else:
if result[col - 1] > 0:
result[col] = result[col - 1] + current_col_sum
elif result[col - 1] <= 0:
result[col] = current_col_sum
col_start = col
if result[col] > self.max_sum:
self.target_sub_matrix_index = [[top_row, bottom_row, col_start, col]]
self.max_sum = result[col]
elif result[col] == self.max_sum:
self.target_sub_matrix_index.append([top_row, bottom_row, col_start, col])
self.check_solution()
def check_solution(self):
extra_solution = []
for solution in self.target_sub_matrix_index:
left_sum_record, right_sum_record = [], []
left_sum = 0
for col in range(solution[2] - 1, -1, -1):
for row in range(solution[0], solution[1] + 1):
left_sum += self.matrix[row][col]
left_sum_record.append(left_sum)
if left_sum == 0:
extra_solution.append([solution[0], solution[1], col, solution[3]])
# right_sum = 0
# if solution[3] != self.cols - 1:
# for col in range(solution[3], self.cols):
# for row in range(solution[0], solution[1] + 1):
# right_sum += self.matrix[row][col]
# right_sum_record.append(right_sum)
# if right_sum == 0:
# extra_solution.append([solution[0], solution[1], solution[2], col])
self.target_sub_matrix_index.extend(extra_solution)
# matrix = [[1, 2, 3, 4], [-1, -2, -3, -4], [-1, 3, 3, 4], [-3, 4, 0, -10]]
# matrix = [[-3, -4, 10, 10], [3, 4, 1, 2]]
# matrix = [[1, -1, 0, 2, 3, -1, 1]]
# matrix = [[0, 0, 2, 3, -5, 5]]
matrix = [[0, 0, 2, 3, 0, 0]]
msm = MaxSubMatrix(matrix)
print(msm.max_sum)
print(msm.target_sub_matrix_index)