美文网首页
从「最大区间和问题」看算法的精进

从「最大区间和问题」看算法的精进

作者: Pope怯懦懦地 | 来源:发表于2022-07-08 01:55 被阅读0次

    最早知道「最大区间和问题」好像是看 Udi Manber 的《算法引论》。最近又在 @吴军 的《计算之魂》1.3 节中看到。

    nums = %w[1.5 -12.3 3.2 -5.5 23.2 3.2 -1.4 -12.2 34.2 5.4 -7.8 1.1 -4.9].map { |e| e.to_f }
    

    O(n^3) 算法

    def max_sum_1(ary)
        n = ary.size
        max_sum = 0
        b, e = 0, 0
    
        n.times do |p|
            (p...n).each do |q|
                # s = ary[p..q].sum
                # 为便于分析算法性能,还是用 for 循环更清晰一些:
                s = 0
                ary[p..q].each { |e| s += e }
    
                if s > max_sum
                    max_sum = s
                    b, e = p, q
                end
            end
        end
        [max_sum, [b, e]]
    end
    
    p max_sum_1(nums) #=> [52.4, [4, 9]]
    

    O(n^2) 算法

    def max_sum_2(ary)
        n = ary.size
        max_sum = 0
        b, e = 0, 0
    
        n.times do |p|
            partial_sum = 0     # 保存 ary[p..(q - 1)].sum
            (p...n).each do |q|
                # puts "#{p} .. #{q}"
                s = partial_sum + ary[q]
    
                if s > max_sum
                    max_sum = s
                    b, e = p, q
                end
    
                partial_sum = s
            end
        end
        [max_sum, [b, e]]
    end
    
    p max_sum_2(nums)
    

    O(n log(n)) 分治算法

    def max_sum_3_using_divide_and_conquer(ary, b, e)
        return [ary[b], [b, e]] if b == e
    
        # -----b_l----e_l----------(mid)---------b_r---------e_r-----➡️
        mid = (b + e) / 2
    
        max_sum_l, interval = max_sum_3_using_divide_and_conquer(ary, b, mid)
        b_l, e_l = interval
    
        max_sum_r, interval = max_sum_3_using_divide_and_conquer(ary, mid + 1, e)
        b_r, e_r = interval
    
        if e_l + 1 == b_r
            if max_sum_l > 0 and max_sum_r > 0
                return [max_sum_l + max_sum_r, [b_l, e_r]]
            else
                if max_sum_l > max_sum_r
                    return [max_sum_l, [b_l, e_l]]
                else
                    return [max_sum_r, [b_r, e_r]]
                end
            end
        else
            maybe_sum = max_sum_l + ary[(e_l + 1)..(b_r - 1)].sum + max_sum_r
            sum2p = {
                maybe_sum => [b_l, e_r],
                max_sum_l => [b_l, e_l],
                max_sum_r => [b_r, e_r]
            }
    
            larger_sum = [maybe_sum, max_sum_l, max_sum_r].max
            return [larger_sum, sum2p[larger_sum]]
        end
    end
    
    p max_sum_3_using_divide_and_conquer(nums, 0, nums.size - 1)
    

    终极皇冠👑:O(n) 线性算法

    @吴军 说是用「正、反两遍扫描」,但理解起来太费劲。其实用递归也能解释(先不考虑边界情况):

    1. 找到第一个为正 + 的数 p_0 ,再找 p_0 之后第一个为负 - 的数 q_0 。计算出 \sum [p_0, q_0) = S_0
    2. 递归寻找 [q_0, e] 段上的最大区间和,记为 \sum [p_k, q_k) = S_k ,同时顺道求出 \sum [q_0, p_k) = \tilde{S}
    3. 剩下的就是常规操作了:
      I. \quad \ \ if \ S_0 + \tilde{S} \ge 0, then \ S_0 + \tilde{S} + S_k \ge S_k ,意味着排除掉 S_k ,需要在 max{ \{ S_0, \ S_0 + \tilde{S} + S_k \} } 中选择。
      II. \quad if \ S_0 + \tilde{S} \lt 0, then \ S_0 + \tilde{S} + S_k \lt S_k ,意味着只需在 max{ \{ S_0, \ S_k \} } 中选择。

    不过坑🕳️还是很多的,昨晚调试到凌晨两点🕑😖。

    def find_the_first_positive_num(ary, b = 0)
      l = nil
      sum_before_l = 0
      ary[b..].each_with_index do |e, i|
        if e >= 0
          l = b + i
          break
        end
        sum_before_l += e
      end
    
      if l.nil?   # 全为负。
        return [nil, nil]
      else
        return [l, sum_before_l]
      end
    end
    
    def find_the_first_negative_num(ary, b = 0)
      l = nil
      sum_before_l = 0
      ary[b..].each_with_index do |e, i|
        if e < 0
          l = b + i
          break
        end
        sum_before_l += e
      end
    
      if l.nil?   # 全为正。
        return [nil, nil]
      else
        return [l, sum_before_l]
      end
    end
    
    def max_sum_4(ary, b, e, depth = 0)
      # return format: [sum_before_l, [l..r], max_sum]
      return [0, [b, e], ary[b]] if b == e if b == e
        
      p0, sum_left_side_0 = find_the_first_positive_num(ary, b)
      
      if p0.nil?  # 全为负数。
        max_negative = ary[b..e].max
        idx = ary.index(max_negative)
    
        return [0, [idx, idx], max_negative]
      else
        l, r, max_sum = nil
    
        q0, sum_p0 = find_the_first_negative_num(ary, p0)
    
        if q0.nil?
          return [ary[b...p0].sum, [p0, e], ary[p0..e].sum] if q0.nil?  # [p0 .. e] 全为正数。
        else
          sum_left_side, interval, max_sum_rest = max_sum_4(ary, q0, e, depth)
          l_rest, r_rest = interval
    
          return [sum_left_side_0, [p0, q0 - 1], sum_p0] if max_sum_rest < 0
    
          if sum_p0 + sum_left_side >= 0
            if sum_left_side + max_sum_rest >= 0
              # 扩展至 [p0 .. r_rest]
              return [sum_left_side_0, [p0, r_rest], sum_p0 + sum_left_side + max_sum_rest]
            else
              # 只保留 [p0 .. q0 - 1]
              return [sum_left_side_0, [p0, q0 - 1], sum_p0]
            end
          else
            if sum_p0 >= max_sum_rest
              return [sum_left_side_0, [p0, q0 - 1], sum_p0]
            else
              return [sum_left_side_0 + sum_p0 + sum_left_side, [l_rest, r_rest], max_sum_rest]
            end
          end
        end
      end
    end
    
    p max_sum_4(nums, 0, nums.size - 1)
    

    相关文章

      网友评论

          本文标题:从「最大区间和问题」看算法的精进

          本文链接:https://www.haomeiwen.com/subject/qirmbrtx.html