子串/子序列最值
Subarray最值简介
注意区分:子串/subarray是一个序列的连续子序列,而子序列/subsequnce不一定需要连续。
在原有数组(或字符串)中寻找子串(或子字符串)的最值,暴力法一般是 O(n³)的时间复杂度。因为枚举首尾需要O(n²),而判断断子串符合要求另需要O(n)。
要把总时间复杂度O(n³)降下来到有O(n²),O(nlogn)甚至O(n),有这么几种方法可以加速:
前缀和
双指针(滑动窗口)
动态规划
二分查找
中心扩展
单调栈
(1) 前缀和
用数组或者哈希表来记录前 个数字的连续和。
参考模版:
presum = [0 for _ in range(len(n)+1)] # sum of nums[:i]
for i in range(n):
presum[i+1] = presum[i] + nums[i]前缀和可以用来加速求解区间和。定义前缀和 ,那么
。
(2) 双指针
参考模版:
这个方法很实用于解决这样一类问题:符合某某要求的最长子串。有一些题目看上去不像子串最值,但是通过转换就变成了““题,可以用滑动窗口求解。
很重要的一点就是:如何定义"符合要求"。下面几个例子感受一下:
简单:
无重复字符的最长子串 [LC3]: 要求=子串无重复字符
最小覆盖子串 [LC76]: 要求=子串含有所有
target元素长度最小的子数组 [LC209]: 要求=子串和
≥target至多包含K个不同字符的最长子串 [LC340]: 要求=子串最多包含K个不同字符
中等:
找到字符串中所有字母异位词 [LC438]: 要求=子串是
target的anagram尽可能使字符串相等 [LC1208]: 要求=子串
cost的和≤budget最高频元素的频数 [LC5739]: 首先排序,要求=(最大元素x子串长度 - 子串和)
≤budget
(3) 动态规划
情况一:一次遍历,用O(1)的空间就能解决。相当于运用了滚动数组的动态规划,因为转移方程中f[i]只依赖于f[i-1]例子有:
最大子序和 [LC53] (延伸:线段树)
情况二:二维DP或区间DP。具体参见动态规划章节。
(4) 二分查找
对“符合某某要求的xx最大值子串“,二分查找的思路一般是通过猜xx最大值->验证可行性->修正的过程。是二分查找中比较难的类型。有的时候要借助于前缀和来帮助快算区间和。
(5) 中心扩展/单调栈
参考代码:
分治:从中间向外贪心展开,左右分别操作,最后合并结果。虽然能得到正确答案,然而时间复杂度为O(nlogn)。
为了寻求O(n)的算法,我们再观察能发现,包含每个点的最大柱形面积为
利用递增单调栈,next shorter index就能通过模版找到。因为是递增栈,所以previous shorter index就是栈中前一个元素(也就是我弹出后剩下的栈顶)。为了防止栈顶没有了元素,我们防御性编程地一开始在数组头尾增加了0。
Subsequence最值
枚举长度为n的序列的所有Subsequence有2ⁿ个,所以枚举肯定会超时。subsequence的最值一般通过O(n²)的动态规划求解。
最长递增子序列 (LIS): 有O(nlogn)的二分解法
最大整除子集 (LDS): 这题本质和LIS一样,源自于
%关系的递推性。LIS中 ,类似的 。所以在两题中,如果
[a,b]是一个答案的话,[a,b,c]是一个更好的答案。最长公共子序列(LCS)
Last updated
Was this helpful?