子串/子序列最值

Subarray最值简介

在原有数组(或字符串)中寻找子串(或子字符串)的最值,暴力法一般是 O(n³)的时间复杂度。因为枚举首尾需要O(n²),而判断断子串符合要求另需要O(n)

要把总时间复杂度O(n³)降下来到有O(n²),O(nlogn)甚至O(n),有这么几种方法可以加速:

  • 前缀和

  • 双指针(滑动窗口)

  • 动态规划

  • 二分查找

  • 中心扩展

  • 单调栈

(1) 前缀和

用数组或者哈希表来记录前 ii 个数字的连续和。

参考模版:

presum = [0 for _ in range(len(n)+1)] # sum of nums[:i]
for i in range(n):
    presum[i+1] = presum[i] + nums[i]

前缀和可以用来加速求解区间和。定义前缀和 Si=n0,1,...,i1S_i = \sum n_{0, 1, ..., i-1} ,那么

ni...j=sj+1si\sum n_{i...j} = s_{j+1} - s_{i}

给定一个二进制数组 nums , 
找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。


输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
def findMaxLength(nums):
    res = 0
    m = {0: -1}
    cnt = 0
    for i, n in enumerate(nums):
        cnt += 1 if n==0 else -1
        if cnt in m:
            res = max(res, i-m[cnt])
        else:
            m[cnt] = i
    return res

(2) 双指针

参考模版:

left = right = 0
window = defaultdict(int)
while right < len(s):
    a = s[right]
    right += 1
    window[a] += 1
    
    while (window[c]...): # check condition
        b = s[left]
        left += 1
        window[d] -= 1
    res = ... # here window contains s[left, right+1]
              # with substring lenGth right-left

这个方法很实用于解决这样一类问题:符合某某要求的最长子串。有一些题目看上去不像子串最值,但是通过转换就变成了““题,可以用滑动窗口求解。

很重要的一点就是:如何定义"符合要求"。下面几个例子感受一下:

简单:

中等:

(3) 动态规划

情况一:一次遍历,用O(1)的空间就能解决。相当于运用了滚动数组的动态规划,因为转移方程中f[i]只依赖于f[i-1]例子有:

情况二:二维DP或区间DP。具体参见动态规划章节。

(4) 二分查找

对“符合某某要求的xx最大值子串“,二分查找的思路一般是通过猜xx最大值->验证可行性->修正的过程。是二分查找中比较难的类型。有的时候要借助于前缀和来帮助快算区间和。

(5) 中心扩展/单调栈

参考代码:

分治:从中间向外贪心展开,左右分别操作,最后合并结果。虽然能得到正确答案,然而时间复杂度为O(nlogn)

def largestFromPoint(heights, m):
    if not heights: return 0
    height, width = heights[m], 1

    res = height
    l, r = m-1, m+1
    while l>=0 or r<len(heights):
        width += 1
        left = heights[l] if l>=0 else float('-inf')
        right = heights[r] if r<len(heights) else float('-inf')
        if left>right:
            l -= 1
        else:
            r += 1
        height = min(height, max(left, right))
        res = max(res, height * width)
    return res

def largestRectangleArea(heights):
    if not heights: return 0
    mid = len(heights) // 2
    me =  self.largestFromPoint(heights, mid)
    left = self.largestRectangleArea(heights[:mid])
    right = self.largestRectangleArea(heights[mid+1:])
    return max(me, left, right)

Subsequence最值

枚举长度为n的序列的所有Subsequence有2ⁿ个,所以枚举肯定会超时。subsequence的最值一般通过O(n²)的动态规划求解。

# DP: O(N^2)
def lengthOfLIS(nums):
    f = [1 for _ in range(len(nums))]
    for i in range(len(nums)):
        for j in range(i):
            if nums[i] > nums[j]:
                f[i] = max(f[i], f[j] + 1)
    return max(f)
    
# 二分查找O(NlogN)解法
def lengthOfLIS(nums):
    f = [float('inf') for _ in range(len(nums))]
    for n in nums:
        f[bisect.bisect_left(f, n)] = n
    return bisect.bisect_left(f, float('inf'))

Last updated

Was this helpful?