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

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

作者: 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)

相关文章

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

    最早知道「最大区间和问题」好像是看 Udi Manber 的《算法引论》[https://book.douban....

  • 算法与数据结构(十) 总结

    课程总结 过程: 线性问题: 树形问题: 图论问题: 更多算法问题 算法设计相关: 贪心:从最小到最大,或从最大到...

  • [算法] 区间问题

    本文对区间查询问题常用的数据结构方法进行总结 1. 前缀和 前缀和是降低区间查询问题复杂度的一种常见预处理方法,对...

  • 算法设计(Jon kleinberg)

    经典问题 稳定匹配及其推广/二分匹配 G-S算法和推广区间调度/带权值的区间调度/多资源区间调度/延迟区间调度/其...

  • 贪婪算法

    贪婪算法:选择局部最优解达到全局最优 区间调度问题 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不...

  • 从最大连续和问题看算法的时间复杂度

    参考紫书8.1章节。最大连续和问题 在给定序列中找到最大连续和,该问题最简单的解答思路是将所有子序列的和求出,并找...

  • 121. Best Time to Buy and Sell S

    最大子段和问题的变形:此题相关的算法是:Kadane算法代码:

  • 基础九 线段树Segment Tree

    线段树功能: O(logN) 找到某区间的 最大最小值 元素个数 区间和 O(1) 得到全部区间的 最大最小值 元...

  • 01.14 - 内存管理

    内存管理 1. 数据的存储 内存分为栈区间和堆区间,从底层看,栈区间的内存的开辟与释放是系统自动管理的,堆区间的内...

  • 线段树(区间树)--Segement Tree

    用于解决的问题: 对于给定区间 更新:更新区间中一个元素或者一个区间的值 查询一个区间[i,j]的最大值,或者区间...

网友评论

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

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