计算之魂 寻找最好的算法

寻找最好的算法

1. 实例

以总和最大区间问题为例:

给定一个实数序列,设计一个最有效的算法,找到一个总和最大的区间

  • 暴力解法1:O(n3),暴力枚举左边界和右边界需要O(n2),计算边界内的和需要O(n),因此时间复杂度为O(n2)
  • 暴力解法2:分析暴力解法1,发现计算边界内的和不需要重复计算,在枚举边界时即可计算出当前最大和,这时需要维护三个变量:当前最大和,当前右边界,当前总和,时间复杂度为O(n2)
  • 分治法:首先,将序列[p, q]以分为二,分别计算左右边界[p, K/2], [K/2+1, q]的最大和 [ p 1 , q 1 ] , [ p 2 , q 2 ] [p_1, q_1], [p_2, q_2] [p1,q1],[p2,q2],当前整个序列的最大和为以下三个值中的最大值(可举例推导为以下三个值中的最大值):
    • [ p 1 , q 1 ] [p_1, q_1] [p1,q1]
    • [ p 2 , q 2 ] [p_2, q_2] [p2,q2]
    • [ p 1 , q 2 ] [p_1, q_2] [p1,q2]
  • 正反两遍扫描:从左到右扫描可以确定右边界,从右到左扫描确定左边界,如果从左到右扫描,出现了和为负数情况,需要将序列以为负数的点为界,分为多个子序列,整个序列的最大和等价于多个子序列最大和中的最大值。
  • 动态规划:这是个典型的动态规划问题,求解最大连续子序列和

2 思考

  • 将上述例题的线性复杂度算法写成伪代码
def solve(arr):
    sum_tmp = 0
    sum_max = arr[0]
    left = 0
    right = 0
    left_tmp = 0
    for i in range(len(arr)):
        sum_tmp += arr[i]
        if sum_tmp > sum_max:
            sum_max = sum_tmp
            right = i
            left = left_tmp
        if sum_tmp < 0:
            left_tmp = i + 1
            sum_tmp = 0
    return sum_max, arr[left: right]


def solve2(arr):
    # if we should find the left and right bound, we can use additional O(n) to store the left bound, and the right bound is the position which is max number in dp
    dp = [0 * len(arr)]
    dp[0] = arr[0]

    for i in range(1, len(arr)):
        dp[i] = max(dp[i - 1] + arr[i], arr[i])

    return max(dp)
  • 在一个数组中寻找一个区间,使得区间内的数字之和等于某个事先给定的数字

指定区间 [ l , r ] = S [ r ] − S [ l − 1 ] [l, r] = S[r] - S[l - 1] [l,r]=S[r]S[l1],其中 S [ i ] S[i] S[i]表示从第一个下表到i的区间和,因此使用一个哈希表存储该值即可,其中的key是区间和,value是下标i,时间复杂度为O(n),空间复杂度为O(n)

思路较简单,这里就不给出代码了。

  • 在一个二维矩阵中,寻找一个矩形的区域,使其中的数字之和达到最大值

关键点:求阶二维矩阵的某个矩形区域,可以转化为一维矩阵的最大和区间,具体转化规则如下:

设原矩阵为m行n列,如下:
( a 0 , 0 a 0 , 1 ⋯ a 0 , n a 1 , 0 a 1 , 1 ⋯ a 1 , n ⋮ ⋮ ⋱ ⋮ a m , 0 a m , 1 ⋯ a m , n ) \left( \begin{matrix} a_{0,0} & a_{0,1} & \cdots & a_{0, n} \\ a_{1,0} & a_{1,1} & \cdots & a_{1, n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,0} & a_{m,1} & \cdots & a_{m, n} \end{matrix} \right) a0,0a1,0am,0a0,1a1,1am,1a0,na1,nam,n
S ( i , j ) S(i, j) S(i,j)表示从(0, 0)到(i, j)的子矩阵和,可以得出,求解第i行到第j行的最大矩阵和,等价于求阶一维数组 ( ∑ x = i ′ j a x , 0 , ⋯   , ∑ x = i ′ j a x , n ) , i ′ ∈ [ i , j ] (\sum_{x=i'}^ja_{x, 0}, \cdots, \sum_{x=i'}^ja_{x, n}), i' \in [i, j] (x=ijax,0,,x=ijax,n),i[i,j]的最大区间和,也就是说求解原来的j-i+1行n列的矩阵最大子矩阵和等价于求阶1行n列的数组最大区间和,其中数组的每一项等于原来的矩阵每一列之和,行数可以从1行到j-i+1行

简单理解如下:
S ( i , j ) = ∑ x = 0 i ∑ y = 0 j a x , y S(i, j) = \sum_{x=0}^{i}\sum_{y=0}^{j}a_{x,y} \\ S(i,j)=x=0iy=0jax,y
上述计算可以先按照列把第0列到第j列按照行相加,得到一行j列的数组,然后按列相加即可得到 S ( i , j ) S(i, j) S(i,j),下式得到的是按列相加得到的一维数组:
( ( a 0 , 0 + a 1 , 0 + ⋯ + a i , 0 ) ( a 0 , 1 + a 1 , 1 + ⋯ + a i , 1 ) ⋯ ( a 0 , j + a 1 , j + ⋯ + a i , j ) ) \left( \begin{matrix} (a_{0, 0} + a_{1, 0} + \cdots + a_{i, 0}) & (a_{0, 1} + a_{1, 1} + \cdots + a_{i, 1}) & \cdots & (a_{0, j} + a_{1, j} + \cdots + a_{i, j}) \end{matrix} \right) ((a0,0+a1,0++ai,0)(a0,1+a1,1++ai,1)(a0,j+a1,j++ai,j))
然后再每个元素相加即得到了 S ( i , j ) S(i, j) S(i,j),因此计算矩阵的最大子矩阵和可以等价于计算多个一维数组的最大区间和。

注:上述一维数组中最外面的括号表示这是一个数组,里面的括号表示这是数组的一个元素

伪代码如下:

def solve(matrix):
    max_sum = matrix[0][0]
    for i in range(0, len(matrix)):
        arr = [0] * len(matrix[0])
        for j in range(i, len(matrix[0])):
            arr = [arr[k] + matrix[j][k] for k in range(len(arr))]
            # find max internal sum in current array
            tmp_max_sum, tmp_left, tmp_right = find_max_internal_sum(arr)
            if tmp_max_sum > max_sum:
                max_sum = tmp_max_sum
                c1 = tmp_left
                c2 = tmp_right
                r1 = i
                r2 = j
	return [r1, c1, r2, c2]

时间复杂度为 O ( m ∗ n ∗ n ) O(m*n*n) O(mnn)

References

  1. 计算之魂思考题1.3

你可能感兴趣的:(计算之魂,算法)