和小于k的最长连续子串

问题

已知一个长度为n的数组(允许负数), 和一个整数k, 求:和小于k的最长连续子串?

例如:

k=184, A=[431, -15, 639, 342, -14, 565, -924, 635, 167, -70],

和小于184的最长子串为A[3..6]

写个简单测试先

require_relative '../longest_subarray_with_sum_less_than_k'

describe LongestSub do
  let(:array) {[431, -15, 639, 342, -14, 565, -924, 635, 167, -70]}
  let(:bound) {184}
  let(:result) {subject.longest(array,bound)}
  let(:ans) {array[3..6]}
  it "should compute the longest subarray whose sum <= bound" do
    expect(result).to eq ans
  end
end

分析

如果暴力枚举起点和终点,复杂度为O(n^2)。能不能找到一些规律优化呢?

Make hands dirty

单纯考虑原数组好像找不到规律。

考虑前缀和,此时问题转换为已知pre数组,求pre[j]-pre[i]<=k的距离最大(i,j)对.

由于有负数,pre数组没有递增规律。

第一个元素的右端点,就已经难以确定了,必须从右到左扫描,直到差值<=k,有可能一个都不成功;而且,算好i后,算i+1时好像还是要扫描所有之后的右端点。

于是考虑对pre排序, 然后可以用“双指针”,不断移动右指针直到>k,并一直保存当前最右下标。这样需要复杂度O(nlogn)排序+O(n) == O(nlogn)

Make it better

还有更好的方法吗?有没有什么规律,来排除一些值呢?

可以发现右端点中,如果j1<j2而且pre[j1]>=pre[j2],那肯定不会选j1, 因为j2既比j1远,求和时结果又比j1小。所以,可以从右到左扫描一遍,过滤掉不可能的右端点。

而且发现,这样过滤后的右端点在pre上是递增的,就可以用“双指针”求了。

复杂度 = O(n)过滤 + O(n)扫描 = O(n)

代码:

class LongestSub
  def longest(arr,bound)
    reset(arr,bound)
    l,r = [(0...size),poss_r].map(&:to_enum)
    loop { update(l,r) }
    arr[@longest_range]
  end

  def update(l, r)
    i,j = [l,r].map(&:peek)
    if i<=j && presum(j,i)<=bound
      @longest_range = [@longest_range, (i..j)].max_by(&:size)
      r.next
    else
      l.next
    end
  end

private
  attr_reader :arr, :bound
  def reset(arr,bound)
    @arr,@bound=[arr,bound]
    @poss_r = @pre  = nil
    @longest_range = (0...0)
  end
  def size; arr.size end
  def pre
    @pre ||= arr.reduce([0]){|res,v| res << res.last+v}
  end
  def presum(j,i=0)
    pre[j+1] - pre[i]
  end
  def poss_r
    @poss_r ||= (0...size).reverse_each.reduce([]) do |res,i|
      res.unshift(i) unless res.first && presum(i) >= presum(res.first)
      res
    end
  end
end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容