Subarray
- 一、前缀和
- 1800.最大升序子数组和
- 525.连续数组
- 523.连续的子数组和
- 974.和可被K整除的子数组
- ★560.和为K的子数组
- 1524.和为奇数的子数组数目
- 1695.删除子数组的最大得分
- 554.砖墙
- 1588.所有奇数长度子数组的和
- 1031.两个非重叠子数组的最大和
- 1508.子数组和排序后的区间和
- 1343.大小为K且平均值大于等于阈值的子数组数目
- 2090.半径为K的子数组平均值
- 152.乘积最大子数组
- 1310.子数组异或查询
- 1749.任意子数组和的绝对值的最大值
- *689.三个无重叠子数组的最大和
- 1712.将数组分成三个子数组的方案数
- 二、滑动窗口
- 643.子数组最大平均数I
- 1695.删除子数组的最大得分
- 209.长度最小的子数组
- 992.K个不同整数的子数组
- 1248.统计「优美子数组」
- 560.和为K的子数组
- 930.和相同的二元子数组
- 795.区间子数组个数
- 713.乘积小于K的子数组
- 2261.含最多K个可整除元素的子数组
- 三、动态规划
- [2321. 拼接数组的最大分数](https://leetcode.cn/problems/maximum-score-of-spliced-array/)
- 1191.K次串联后最大子数组之和
- 1477.找两个和为目标值且不重叠的子数组
- 898.子数组按位或操作
- 978.最长湍流子数组
- 718.最长重复子数组
- 四、单调栈
- 907.子数组的最小值之和
- 581.最短无序连续子数组
- ★2104.子数组范围和
- 1856.子数组最小乘积的最大值
- 四、综合题:前缀和、单调队列,滑动窗口。
- ★862.和至少为K的最短子数组
- 1438.绝对差不超过限制的最长连续子数组
- 1793.好子数组的最大分数
- *1157.子数组中占绝大多数的元素
- 1186.删除一次得到子数组最大和
- *1330.翻转子数组得到最大的数组值
- *1460.通过翻转子数组使两个数组相等
- *1526.形成目标数组的子数组最少增加次数
- *1546.和为目标值且不重叠的非空子数组的最大数目
- 1567.乘积为正数的最长子数组长度
- *1569.将子数组重新排序得到同一个二叉查找树的方案数
- *1574.删除最短的子数组使剩余数组有序
- 1630.等差子数组
- 1764.通过连接另一个数组的子数组得到一个数组
- 2261.含最多K个可整除元素的子数组
- [6248. 统计中位数为 K 的子数组](https://leetcode.cn/problems/count-subarrays-with-median-k/)
一、前缀和
1800.最大升序子数组和
class Solution:
def maxAscendingSum(self, nums: List[int]) -> int:
ans, left, pre = 0, 0, -1
acc = [0] + list(accumulate(nums))
for i, x in enumerate(nums):
if x <= pre: left = i
pre = x
ans = max(ans, acc[i+1] - acc[left])
return ans
525.连续数组
把 0 当作 -1 来统计前缀和,用哈希表记录前缀和第一次出现的下标,两个位置的前缀和相同说明,中间子数组(不包含第一个包含第二个位置)的和为 0, 也就是 0 和 1 个数相等。
class Solution:
def findMaxLength(self, nums: List[int]) -> int:
ans, acc, d = 0, 0, {0:-1} # 求长度 -1,子数组个数 1
for i, x in enumerate(nums):
acc += 1 if x else -1 # 把 0 当作 -1 来统计
if acc in d: ans = max(ans, i - d[acc]) # 更新长度
else: d[acc] = i # key 前缀和,value 索引,保存第一次出现时的下标
return ans
class Solution {
public int findMaxLength(int[] nums) {
int ans = 0, n = nums.length, acc = 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
for (int i = 0; i < n; i++){
acc += (nums[i] == 0) ? -1 : 1;
if (map.containsKey(acc)){
ans = Math.max(ans, i - map.get(acc));
} else map.put(acc, i);
}
return ans;
}
}
523.连续的子数组和
两个整数 a、b,若它们除以整数 m 所得的余数相等,则称 a 与 b 对于模 m 同余或 a 同余于 b 模 m
记作 a≡b (mod m)
读作 a 同余于 b 模 m,或读作 a 与 b 对模 m 同余。
class Solution:
def checkSubarraySum(self, nums: List[int], k: int) -> bool:
acc, d = 0, {0:-1} # value 作为下标用
for i, x in enumerate(nums):
acc += x
key = acc % k # 同余
if key in d:
if i - d[key] >= 2: return True
else: d[key] = i # 保存最小索引
return False
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int acc = 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
for (int i = 0; i < nums.length; i++){
acc += nums[i];
int rem = acc % k;
if (map.containsKey(rem)){
if (i - map.get(rem) >= 2) return true;
}
else map.put(rem, i);
}
return false;
}
}
974.和可被K整除的子数组
同余定理:(a - b) % k = 0 等价于 a % k = b % k
class Solution:
def subarraysDivByK(self, nums: List[int], k: int) -> int:
ans, acc, rem = 0, 0, [0] * k # 保存余数
for x in nums:
rem[acc % k] += 1 # 统计个数用 {0 : 1}
acc += x
ans += rem[acc % k]
return ans
class Solution {
public int subarraysDivByK(int[] nums, int k) {
int ans = 0, acc = 0;
int[] d = new int[k];
d[0] = 1;
for (int x : nums){
acc += x;
ans += d[(acc % k + k) % k]++;
}
return ans;
}
}
class Solution {
public int numOfSubarrays(int[] arr) {
int[] cnt = {0, 0};
int acc = 0, ans = 0;
for (int x : arr) {
cnt[acc % 2]++;
acc += x;
ans += cnt[1 - acc % 2];
ans %= 1000000007;
}
return ans;
}
}
★560.和为K的子数组
借鉴 两数和 的思路 ,利用哈希表。这里是两个前缀和的差。
遍历数组,根据当前项前缀和 acc,在 map 中寻找差为 k 的历史前缀和 pre 即 acc - k。acc - pre = k,求区间的和转换为求前缀和的差。
添加哨兵 d[0] = 1,解决前缀和为 k 的情况。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
ans, acc, d = 0, 0, defaultdict(int)
for x in nums:
d[acc] += 1 # 统计前一个前缀和,默认 {0 : 1}
acc += x # 当前项的前缀和
ans += d[acc - k] # 在 d 中找差为 k 的前缀和 pre, acc - pre = k,即 pre = acc - k
return ans
1524.和为奇数的子数组数目
class Solution:
def numOfSubarrays(self, arr: List[int]) -> int:
acc, ans, d = 0, 0, [0, 0]
for x in arr:
d[acc % 2] += 1
acc += x
ans += d[1 - acc % 2]
return ans % 1000000007
class Solution {
public int subarraySum(int[] nums, int k) {
int res = 0, acc = 0;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++){
map.put(acc, map.getOrDefault(acc, 0) + 1);
acc += nums[i];
res += map.getOrDefault(acc - k, 0);
}
return res;
}
}
1695.删除子数组的最大得分
class Solution:
def maximumUniqueSubarray(self, nums: List[int]) -> int:
res, left, d = 0, 0, defaultdict(int)
pre = [0]+list(accumulate(nums)) # 哨兵 前缀和
for i, x in enumerate(nums):
if x in d and d[x] >= left: left = d[x] + 1 # 只考虑从 left 开始的重复
else: res = max(res, pre[i + 1] - pre[left])
d[x] = i
return res
554.砖墙
class Solution:
def leastBricks(self, wall: List[List[int]]) -> int:
d = {}
for w in wall:
acc = 0 # 用前缀和表示砖缝
for x in w[:-1]: # 不包含最后一块砖
acc += x
d[acc] = d.get(acc, 0) + 1
return len(wall) - max(d.values(), default = 0)
class Solution {
public int leastBricks(List<List<Integer>> wall) {
Map<Integer, Integer> cnt = new HashMap<>();
for (List<Integer> w : wall) {
int acc = 0;
for (int i = 0; i < w.size() - 1; i++) {
acc += w.get(i);
cnt.put(acc, cnt.getOrDefault(acc, 0) + 1);
}
}
int max = 0;
for (int i : cnt.values()){
max = Math.max(max, i);
}
return wall.size() - max;
}
}
1588.所有奇数长度子数组的和
class Solution:
def sumOddLengthSubarrays(self, arr: List[int]) -> int:
ans, pre, n = 0, 0, len(arr)
acc = list(accumulate(arr))
for i in range(n):
# 以下标 i 开头的长度为奇数的子数组和
ans += sum(acc[j] - pre for j in range(i, n, 2))
pre = acc[i]
return ans
class Solution {
public int sumOddLengthSubarrays(int[] arr) {
int n = arr.length, pre = 0, ans = 0;
for (int i = 1; i < n; i++) arr[i] += arr[i - 1];
for (int i = 0; i < n; i++){
for (int j = i; j < n; j += 2){
ans += arr[j] - pre;
}
pre = arr[i];
}
return ans;
}
}
1031.两个非重叠子数组的最大和
class Solution:
def maxSumTwoNoOverlap(self, nums: List[int], firstLen: int, secondLen: int) -> int:
n, a, b = len(nums), firstLen, secondLen
acc = list(accumulate(nums))
ans = acc[a + b - 1]
maxf = acc[a - 1]
maxs = acc[b - 1]
for i in range(a + b, n):
maxf = max(maxf, acc[i-b] - acc[i-a-b]) # acc[a] - acc[0] 第一段
maxs = max(maxs, acc[i-a] - acc[i-a-b]) # acc[b] - acc[0] 第一段
ans = max(ans, max(maxf + acc[i] - acc[i-b], maxs + acc[i] - acc[i-a])) # 第一段 + 笫二段 a + b, b + a
return ans
class Solution {
public int maxSumTwoNoOverlap(int[] nums, int firstLen, int secondLen) {
int a = firstLen, b = secondLen, n = nums.length;
for (int i = 1; i < n; i++) nums[i] += nums[i-1];
int ans = nums[a+b-1], x = nums[a-1], y = nums[b-1];
for (int i = a + b; i < n; i++){
x = Math.max(x, nums[i-b] - nums[i-a-b]);
y = Math.max(y, nums[i-a] - nums[i-a-b]);
ans = Math.max(ans, Math.max(x + nums[i] - nums[i-b], y + nums[i] - nums[i-a]));
}
return ans;
}
}
1508.子数组和排序后的区间和
class Solution:
def rangeSum(self, nums: List[int], n: int, left: int, right: int) -> int:
acc = [0]+list(accumulate(nums))
res = sorted([acc[j] - acc[i] for i in range(n) for j in range(i+1, n+1)])
return sum(res[left-1:right]) % (10**9 + 7)
1343.大小为K且平均值大于等于阈值的子数组数目
class Solution:
def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
# acc = [0] + list(accumulate(arr))
# ans = 0
# for i in range(len(arr) - k + 1):
# if (acc[i + k] - acc[i]) // k >= threshold:
# ans += 1
# return ans
ans, acc = 0, sum(arr[:k-1]) # 差一个
for i, x in enumerate(arr[k-1:]):
acc += x
if acc // k >= threshold: ans += 1
acc -= arr[i]
return ans
2090.半径为K的子数组平均值
class Solution:
def getAverages(self, nums: List[int], k: int) -> List[int]:
n, t = len(nums), 2*k + 1
ans = [-1] * n
acc = sum(nums[:t-1]) # 差一个
for i in range(k, n - k):
# i = k 时 acc[2*k+1] - acc[0] 对应 nums[2*k] 的前缀和
acc += nums[i+k] # t 个 补一个
ans[i] = acc // t
acc -= nums[i-k] # 减一个
return ans
152.乘积最大子数组
class Solution:
def maxProduct(self, nums: List[int]) -> int:
acc, ans, negmax = 1, -inf, -inf # 记录负数最大值
for x in nums:
acc *= x
ans = max(ans, acc, x) # nums 和 acc 的最大值 更新答案
if acc == 0: # 重新初始化
acc, negmax = 1, -inf
elif acc < 0:
if negmax != -inf:
ans = max(ans, acc // negmax)
negmax = max(negmax, acc)
return ans
1310.子数组异或查询
class Solution:
def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]:
acc = [0]
for i, x in enumerate(arr):
acc.append(acc[-1]^x)
return [acc[i]^acc[j+1] for i, j in queries]
1749.任意子数组和的绝对值的最大值
class Solution:
def maxAbsoluteSum(self, nums: List[int]) -> int:
acc = list(accumulate(nums))
return max(abs(max(acc) - min(acc)), abs(max(acc)), abs(min(acc)))
*689.三个无重叠子数组的最大和
1712.将数组分成三个子数组的方案数
class Solution:
def waysToSplit(self, nums: List[int]) -> int:
n, ans = len(nums), 0
acc = list(accumulate(nums))
for i in range(n):
if acc[i] > acc[-1] // 3: break # 剪枝
# 左边: nums[:i+1]和 acc[i], i 确定了第一个
# 中间: 右边界最小为 a 即前一个的二倍,最大是 acc[i] + (acc[-1] - acc(i)) // 2, 共有 b - a 种选择
# 中间:[i+1, x] x ∈ [a, b)
#[0, i] (i, x) [x, -1]
a = bisect_left(acc, 2 * acc[i], i + 1, n - 2)
# 剩余的中间位置
b = bisect_right(acc, (acc[i] + acc[-1] ) / 2 , a, n - 1)
ans += b - a
return ans % 1000000007
二、滑动窗口
643.子数组最大平均数I
class Solution:
def findMaxAverage(self, nums: List[int], k: int) -> float:
s = res = sum(nums[:k])
n = len(nums)
for i in range(k, n):
s += nums[i] - nums[i - k] # 固定窗口移动
res = max(res, s)
return res / k
class Solution {
public double findMaxAverage(int[] nums, int k) {
int sum = 0;
for (int i = 0; i < k; i++) sum += nums[i];
int average = sum;
for (int i = k; i < nums.length; i++){
sum += nums[i] - nums[i - k];
average = Math.max(average, sum);
}
return (double)average / k;
}
}
1695.删除子数组的最大得分
class Solution:
def maximumUniqueSubarray(self, nums: List[int]) -> int:
d, left, sum_, res = defaultdict(bool), 0, 0, 0
for i, v in enumerate(nums):
if d[v]:
res = max(res, sum_)
while nums[left] != v:
sum_ -= nums[left]
d[nums[left]] = False
left += 1
left += 1
else:
sum_ += v
d[v] = True
return max(res, sum_)
# 前缀和
res, left, d = 0, 0, defaultdict(int)
pre = [0]+list(accumulate(nums)) # 哨兵 前缀和
for i, x in enumerate(nums):
if x in d and d[x] >= left: left = d[x] + 1 # 只考虑从 left 开始的重复
else: res = max(res, pre[i + 1] - pre[left])
d[x] = i
return res
class Solution {
public int maximumUniqueSubarray(int[] nums) {
int res = 0, left = 0;
Map<Integer, Integer> map = new HashMap<>();
int[] pre = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++){
int x = nums[i];
pre[i + 1] = pre[i] + x; // 求前缀和
if (map.containsKey(x) && map.get(x) >= left) left = map.get(x) + 1;
else res = Math.max(res, pre[i + 1] - pre[left]);
map.put(x, i);
}
return res;
}
}
209.长度最小的子数组
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
res, left, s = inf, 0, 0 # ▲ 最大值可取 len(nums) + 1
for i, x in enumerate(nums):
s += x
while s >= target: # 满足前提条件找最小
res = min(res, i - left + 1)
s -= nums[left]
left += 1
return res if res != inf else 0 # ▲ sum(nums) < target 的情况
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int res = nums.length + 1, left = 0, sum = 0;
for (int i = 0; i < nums.length; i++){
sum += nums[i];
while (sum >= target){ // 满足条件中求最小
res = Math.min(res, i - left + 1); // 更新结果
sum -= nums[left];
left++; // 收缩左边界
}
}
return res == nums.length + 1 ? 0 : res;
}
}
992.K个不同整数的子数组
把 「恰好」 转换成为 「最多」,「最多存在 K 个不同整数的子区间的个数」与「恰好存在 K 个不同整数的子区间的个数」的差恰好等于「最多存在 K - 1 个不同整数的子区间的个数」。
class Solution {
public int subarraysWithKDistinct(int[] nums, int k) {
return atMostK(nums, k) - atMostK(nums, k - 1);
}
private int atMostK(int[] nums, int k){ // 解法:同 904. 水果成篮
int n = nums.length, left = 0, res = 0;
int[] d = new int[n + 1];
for (int i = 0; i < n; i++){
if (d[nums[i]] == 0) k--;
d[nums[i]]++;
while (k == -1){
d[nums[left]]--;
if (d[nums[left]] == 0) k++;
left++;
}
res += i - left; // [left, i) 区间的长度就是对结果的贡献
}
return res;
}
}
1248.统计「优美子数组」
class Solution:
def numberOfSubarrays(self, nums: List[int], k: int) -> int:
return self.atMostK(nums, k) - self.atMostK(nums, k - 1)
def atMostK(self, nums, k):
# ans, left = 0, deque([-1])
# for i, x in enumerate(nums):
# if x % 2: left.append(i); k -= 1
# if k < 0: left.popleft(); k += 1
# ans += i - left[0]
ans, left = 0, -1
for i, x in enumerate(nums):
k -= x % 2 # 奇数减一
while k < 0:
left += 1 # 先加后判断
k += nums[left] % 2
ans += i - left # + 以索引 i 结尾的子数组个数
return ans
560.和为K的子数组
k 可为负,不象 930。
930.和相同的二元子数组
前缀和数组 acc,子数组 (i, j] 的和为 goal,即:acc[j] − acc[i] = goal。枚举 j ,每次查询满足该等式的 i 的数量。
用哈希表记录每一种前缀和出现的次数,当前枚举到元素 nums[j],查询哈希表中元素 acc[j] − goal,即对应了以当前 j 值为右边界的满足条件的子数组的数量。最后这些元素的总数量即为所有和为 goal 的子数组数量。
实时地更新哈希表,以防止出现 i ≥ j 的情况。
class Solution:
def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
## 方法一:前缀和 哈希表
ans = acc = 0 # 计算前缀和,不用实现前缀和数组
d = defaultdict(int) # 统计出现前缀和 acc 的个数
for x in nums:
d[acc] += 1
acc += x
ans += d[acc - goal]
return ans
class Solution {
public int numSubarraysWithSum(int[] nums, int goal) {
int ans = 0, acc = 0;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++){
map.put(acc, map.getOrDefault(acc, 0) + 1);
acc += nums[i];
ans += map.getOrDefault(acc - goal, 0);
}
return ans;
}
}
求和正好是 goal 的问题转换为求和 ≤ goal 的问题。
class Solution:
def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
## 方法二:滑动窗口 atMostK
def atMostK(nums, k): # 求和 ≤ goal 的子数组
if k < 0: return 0 # goal = 0 的情况
left = res = 0
for i, x in enumerate(nums):
k -= x
while k < 0:
k += nums[left]
left += 1
res += i - left + 1 # 元素个数也是了数组的个数
return res
return atMostK(nums, goal) - atMostK(nums, goal - 1)
class Solution {
public int numSubarraysWithSum(int[] nums, int goal) {
return atMostK(nums, goal) - atMostK(nums, goal - 1);
}
private int atMostK(int[] nums, int k){
if (k < 0) return 0;
int res = 0, left = 0;
for (int i = 0; i < nums.length; i++){
k -= nums[i];
while (k < 0){
k += nums[left];
left++;
}
res += i - left + 1;
}
return res;
}
}
795.区间子数组个数
最大元素在范围 [left, right] 内,即:元素 ≤ R 且至少包含一个 ≥ L 的子数组。
转换为所有元素 ≤ R 的子数组,再从中减去只包含元素 < L 即:≤ L - 1 的子数组。
class Solution:
def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int:
def atMostK(k):
ans = left = 0
for i, x in enumerate(nums):
if x > k: left = i + 1
ans += i - left + 1 # ? 加多少都行,可能是两个相减抵消了。
return ans
return atMostK(right) - atMostK(left - 1)
class Solution:
def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int:
start, res, cnt = -1, 0, 0
for i, x in enumerate(nums):
if x > right: start = i # 重新开始
if x >= left: cnt = i - start # 基数
res += cnt
return res
713.乘积小于K的子数组
class Solution:
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
ans, prod, left = 0, 1, 0
for i, x in enumerate(nums):
prod *= x
while left <= i and prod >= k: # k = 0 的情况
prod //= nums[left]
left += 1
ans += i - left + 1
return ans
2261.含最多K个可整除元素的子数组
class Solution:
def countDistinct(self, nums: List[int], k: int, p: int) -> int:
s, n = set(), len(nums)
for i in range(n):
count = 0
for j in range(i, n):
if nums[j] % p == 0: count += 1
if count > k: break
s.add(tuple(nums[i:j+1]))
return len(s)
class Solution {
public int countDistinct(int[] nums, int k, int p) {
Set<List<Integer>> set = new HashSet<>(); // 自动去重
for (int i = 0; i < nums.length; i++) {
int count = 0; // 计数
List<Integer> list = new ArrayList<>();
for(int j = i; j < nums.length; j++) {
if(nums[j] % p == 0) count++;
if(count > k) break;
list.add(nums[j]);
set.add(new ArrayList(list));
}
}
return set.size();
}
}
三、动态规划
2321. 拼接数组的最大分数
class Solution:
def maximumsSplicedArray(self, nums1: List[int], nums2: List[int]) -> int:
n = len(nums1)
s, t = sum(nums1), sum(nums2)
premax = mx = 0
premin = mn = inf
for a, b in zip(nums1, nums2):
x = a - b
premax = max(x, premax + x)
premin = min(x, premin + x)
mx = max(mx, premax)
mn = min(mn, premin)
return max(t + mx, s - mn)
1191.K次串联后最大子数组之和
分三种情况
1、k = 1: 同 53 题 注意 答案非负数
2、k = 2: 最大前缀和 + 最大后缀和, 其实两个合起来同 1
3、k > 2: sum < 0, 同 2;否则 2 答案 + sum * (k - 2)
class Solution:
def kConcatenationMaxSum(self, arr: List[int], k: int) -> int:
a = arr * min(k, 2)
ans = pre = 0 # 答案最小为 0
for i, x in enumerate(a):
pre = max(x, pre + x)
ans = max(ans, pre)
s = sum(arr)
if s > 0 and k > 2:
ans = max(ans, s * (k - 2) + ans)
return ans % 1000000007
1477.找两个和为目标值且不重叠的子数组
借鉴 两数之和 的解法,寻找和为 target 的子数组。
用动态规划找两个互不重叠的、长度和最小的子数组 。
dp[i] 表示当前位置 i 之前最短的一个长度。
所以我们的目的就是找出另外一个最短的满足条件的子数组。
class Solution:
def minSumOfLengths(self, arr: List[int], target: int) -> int:
acc, d, n = 0, {0:-1}, len(arr)
ans, dp = n + 1, [n] * n
for i, x in enumerate(arr):
acc += x
y = acc - target
if y in d:
curlen = i - d[y]
# 更新答案,当前子数组长度 + 前一个 dp[d[y]], 同时保证了不重复。
ans = min(ans, curlen + dp[d[y]])
dp[i] = min(curlen, dp[i - 1]) # 更新当前的最小长度 状态转移 ⑴
else: dp[i] = dp[i - 1] # 状态转移 ⑵
d[acc] = i
return -1 if ans == n + 1 else ans
898.子数组按位或操作
class Solution:
def subarrayBitwiseORs(self, arr: List[int]) -> int:
ans, pre = set(), set()
for i, x in enumerate(arr):
cur = {x}
for y in pre:
cur.add(x | y) # 按位或
ans |= cur # 并集
pre = cur
return len(ans)
978.最长湍流子数组
class Solution:
def maxTurbulenceSize(self, arr: List[int]) -> int:
ans, left, n, pre = 1, 0, len(arr), -1 # 开始不确定
for i in range(1, n):
# 分三种情况
if arr[i] > arr[i - 1]: cur = 1
elif arr[i] < arr[i - 1]: cur = -1
else: cur = 0
if cur == 0: left = i
elif cur == pre: left = i - 1
# cur = 1 if arr[i] > arr[i - 1] else -1 if arr[i] < arr[i - 1] else 0
# left = i if cur == 0 else i - 1 if cur == pre else left
pre, ans = cur, max(ans, i - left + 1)
return ans
class Solution {
public int maxTurbulenceSize(int[] arr) {
int ans = 1, pre = -1, left = 0;
for (int i = 1; i < arr.length; i++){
int cur = 0;
if (arr[i] > arr[i - 1]) cur = 1;
else if (arr[i] < arr[i - 1]) cur = -1;
else cur = 0;
if (cur == 0) left = i;
else if (pre == cur) left = i -1;
ans = Math.max(ans, i - left + 1);
pre = cur;
}
return ans;
}
}
718.最长重复子数组
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
n, m = len(nums2), len(nums1)
#dp = [[0] * (n + 1) for _ in range(m + 1)]
dp = [0] * (n + 1)
ans = 0
for i in range(m):
# # 逆序遍历可降维,正序需要滚动数组
# for j in range(n-1,-1,-1):
# if nums1[i] == nums2[j]:
# dp[j+1] = dp[j] + 1
# ans = max(ans, dp[j+1])
# else:dp[j+1] = 0
tmp = [0] * (n + 1) # 滚动数组
for j in range(n):
if nums1[i] == nums2[j]:
#dp[i+1][j+1] = dp[i][j] + 1
#ans = max(ans, dp[i+1][j+1])
tmp[j+1] = dp[j] + 1
ans = max(ans, tmp[j+1])
dp = tmp
return ans
四、单调栈
907.子数组的最小值之和
数组中每个元素 E = arr[i] 作为最小值的范围 (L, R) ,子数组个数为 count = (i - L) * (R - i ),元素 E 的总贡献值为 arr[i] * count。计算出每个元素的贡献值,然后求和。
利用单调栈 q,找出 arr[q[-1]] 作为最小值的范围,即 (q[-2], i) , Subarray 个数,分两步 左 i - j 个选择,右 j - q[-1] 个选择。
class Solution:
def sumSubarrayMins(self, arr: List[int]) -> int:
arr.append(-1) # 添加哨兵,最后会计算 q 中所有的下标,除 -1 和 len(arr) - 1 外。
q, ans = [-1], 0 # -1 充当开头的下标,事实是末尾的下标,arr[-1] = -1。
for i, x in enumerate(arr):
while x < arr[q[-1]]: # 遇到更小的数作为右边界计算前面的值,当前数先加入后计算。
j = q.pop()
ans += arr[j]*(i - j)*(j - q[-1])
q.append(i)
return ans % (10**9+7)
class Solution {
public int sumSubarrayMins(int[] arr) {
int ans = 0, n = arr.length, mod = 1_000_000_007;
// Deque 18 ms
// Deque<Integer> q = new ArrayDeque<>();
// for (int i = 0; i <= n; i++){
// int cur = i == n ? -1 : arr[i]; // 相当于加了哨兵
// while (!q.isEmpty() && cur < arr[q.peek()]) {
// int j = q.pop();
// int k = q.isEmpty() ? -1 : q.peek();
// long tmp = (long)arr[j] * (j - k) * (i - j) % mod;
// ans = (ans + (int)tmp) % mod;
// }
// q.push(i);
// Array 6 ms
int idx = -1;
int[] q = new int[n];
for (int i = 0; i <= n; i++){
int cur = i == n ? 0 : arr[i];
while (idx != -1 && cur < arr[q[idx]]) {
// k 左边界,i 右边界,j 中间
int j = q[idx--];
int k = idx == -1 ? -1 : q[idx];
long tmp = (long)arr[j] * (j - k) * (i - j) % mod;
ans = (ans + (int)tmp) % mod;
}
q[++idx] = i;
}
return ans;
}
}
class Solution {
public int sumSubarrayMins(int[] arr) {
long res = 0L;
for (int i = 0; i < arr.length; i++){
int min = arr[i];
for (int j = i; j < arr.length; j++){
min = Math.min(min, arr[j]);
res += min;
res %= 1000000007; // 过 78 个
// if (res > 1000000007) res -= 1000000007; // 过 83 个
}
}
return (int)res % 1000000007;
}
}
581.最短无序连续子数组
class Solution:
def findUnsortedSubarray(self, nums: List[int]) -> int:
q, n = [], len(nums)
left, right = len(nums) - 1, 0
mx, mn = nums[0], nums[-1]
for i, x in enumerate(nums):
'''
# 利用单调栈找左边界
while q and x < nums[q[-1]]:
left = min(left, q.pop())
q.append(i)
'''
# 逆序遍历,记录大于当前最小值的下标
y = nums[-i -1]
if y > mn: left = n-i-1
else: mn = y
# 记录小于当前最大值的下标
if x < mx: right = i
else: mx = x
return 0 if right == 0 else right - left + 1
class Solution {
public int findUnsortedSubarray(int[] nums) {
int n = nums.length, left = n - 1, right = 0, max = nums[0], min = nums[n - 1];
for (int i = 0; i < n; i++){
int x = nums[i], y = nums[n-i-1];
if (y > min) left = n-i-1;
else min = y;
if (x < max) right = i;
else max = x;
}
return right == 0 ? 0 : right - left + 1;
}
}
★2104.子数组范围和
所有子数组范围的和 = 所有子数组的最大值的和 A - 所有子数组的最小值的和 B。
A = sum(a * k for a in nums) # k 是以 a 为最大值的子数组数目,ak 即 a 对和的贡献。
假设 a = nums[i],a 的左右第一个比 a 大的数的索引分别是 left 和 right,k = (right − i) × (i − left),其中包含 i 的区间左端点数为 i - left 个,右端点数为 right - i 个,分步为乘法。a 作为最大值的最大区间为 [left + 1, right - 1]
class Solution:
def subArrayRanges(self, nums: List[int]) -> int:
nums.append(inf)
# 单调递减栈
q, res = [-1], 0
for r, e in enumerate(nums):
# 遇到比栈顶大的数,终结栈顶元素。
while q and e > nums[q[-1]]:
i = q.pop()
left = q[-1] # if q else -1 # 在 q 预先加入 -1
res += nums[i] * (i - left) * (r - i)
q.append(r)
# 单调递增栈
q, nums[-1] = [-1], -inf
for r, e in enumerate(nums):
while q and e < nums[q[-1]]:
i = q.pop()
left = q[-1]
res -= nums[i] * (i - left) * (r - i)
q.append(r)
return res
# 方法:暴力+滑动窗口,从起点开始遍历更新最值
n, res = len(nums), 0
for i in range(n):
mi = ma = nums[i]
for j in range(i + 1, n):
ma = max(ma, nums[j])
mi = min(mi, nums[j])
res += ma - mi
return ans
class Solution {
public long subArrayRanges(int[] nums) {
int n = nums.length;
long res = 0;
ArrayDeque<Integer> q = new ArrayDeque<>();
int[] x = Arrays.copyOf(nums, n + 1);
x[n] = Integer.MAX_VALUE;
for (int i = 0; i <= n; i++){
while (!q.isEmpty() && x[i] > x[q.peek()]){
int j = q.pop();
res += (long)x[j] * (j - (q.isEmpty() ? -1 : q.peek())) * (i - j);
}
q.push(i);
}
q.clear();
x[n] = Integer.MIN_VALUE;
for (int i = 0; i <= n; i++){
while (!q.isEmpty() && x[i] < x[q.peek()]){
int j = q.pop();
res -= (long)x[j] * (j - (q.isEmpty() ? -1 : q.peek())) * (i - j);
}
q.push(i);
}
return res;
// 优化:用数组代替栈
// int n = nums.length, top = 0;
// long res = 0;
// int[] q = new int[n + 1]; // 存储下标,这里先放个 -1
// q[0] = -1;
// for (int i = 0; i <= n; i++){
// while (top > 0 && (i == n || nums[q[top]] < nums[i])){
// int j = q[top--];
// res += 1L * nums[j] * (i - j) * (j - q[top]);
// }
// q[++top] = i;
// }
// top = 0;
// q[0] = -1;
// for (int i = 0; i <= n; i++){
// while (top > 0 && (i == n || nums[q[top]] > nums[i])){
// int j = q[top--];
// res -= 1L * nums[j] * (i - j) * (j - q[top]);
// }
// q[++top] = i;
// }
// return res;
}
}
1856.子数组最小乘积的最大值
枚举「最小值」,数组中的每个元素 nums[i] 作为最小值,使用单调栈快速寻找左右两边不小于他的边界。
class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
mod, n = 10**9 + 7, len(nums)
left, right = [0] * n, [n - 1] * n
q = list() # 单调栈 确定左右边界
for i, num in enumerate(nums):
while q and nums[q[-1]] >= num: # 非严格
right[q[-1]] = i - 1 # right[i] 是右侧最近的小于等于 nums[i] 的元素下标
q.pop()
if q:
left[i] = q[-1] + 1 # left[i] 是左侧最近的严格小于 nums[i] 的元素下标
q.append(i)
# 前缀和
acc = [0] + list(accumulate(nums))
ans = max((acc[right[i] + 1] - acc[left[i]]) * x for i, x in enumerate(nums))
return ans % mod
四、综合题:前缀和、单调队列,滑动窗口。
★862.和至少为K的最短子数组
class Solution:
def shortestSubarray(self, nums: List[int], k: int) -> int:
n = len(nums)
ans, q = n + 1, collections.deque() # 双端队列
acc = [0] + list(accumulate(nums)) # 前缀和
for i, x in enumerate(acc):
# [84,-37,32,40,95] [0, 84, 47, 79, 119, 214]
# 左边前缀和越小,子数组和越大,长度超短。如:47 取代了 84
# q 是单调递增的,保证了左边右移,子数组的和越来越小,可找到最小窗口。
while q and x <= acc[q[-1]]: q.pop() # 单调队列
while q and x - acc[q[0]] >= k: # 滑动窗口
ans = min(ans, i - q.popleft())
q.append(i) # 下标
return ans if ans <= n else -1
class Solution {
public int shortestSubarray(int[] nums, int k) {
int n = nums.length;
int ans = n + 1;
long[] acc = new long[n + 1];
Deque<Integer> q = new LinkedList();
for (int i = 0; i < n; i++) acc[i + 1] = acc[i] + (long) nums[i];
for (int i = 0; i < acc.length; i++){
while (!q.isEmpty() && acc[i] <= acc[q.getLast()]) q.removeLast();
while (!q.isEmpty() && acc[i] >= acc[q.getFirst()] + k) ans = Math.min(ans, i - q.removeFirst());
q.addLast(i);
}
return ans <= n ? ans : -1;
}
}
1438.绝对差不超过限制的最长连续子数组
from sortedcontainers import SortedList
class Solution:
def longestSubarray(self, nums: List[int], limit: int) -> int:
# 方法一、滑动窗口 有序列表
s = SortedList()
left = ans = 0
for i, x in enumerate(nums):
s.add(x)
if s[-1] - s[0] > limit:
s.remove(nums[left])
left += 1
return len(nums) - left
# 方法二、滑动窗口 单调栈
mx, mn = deque(), deque()
left = 0
for i, x in enumerate(nums):
while mx and mx[-1] < x:
mx.pop()
while mn and mn[-1] > x:
mn.pop()
mx.append(x)
mn.append(x)
if mx[0] - mn[0] > limit:
if mx[0] == nums[left]:
mx.popleft()
if mn[0] == nums[left]:
mn.popleft()
left += 1
return len(nums) - left
1793.好子数组的最大分数
以每一个数作为最小值的作用范围,但必须包含 nums[k],所以超过 nums[k] 的数就不用考虑了。
class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
q, n = [], len(nums)
ans = 0
for i, x in enumerate(nums):
heappush(q, (x, i))
i, j = 0, n - 1
while q:
x, idx = heappop(q)
if idx > j or idx < i:continue # 区间以外
ans = max(ans, x*(j-i+1))
if idx > k: j = idx - 1
elif idx < k: i = idx + 1
else: break # 正好是 nums[k]
return ans
其实只要排序的列表就可以了。
class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
ans, n, t = 0, len(nums), nums[k]
# 大于 t 数就不用考虑了,因为用它作为最小值时一定不包含 t
q = [(x, i) for i, x in enumerate(nums) if x <= t]
q.sort(reverse=True)
i, j = 0, n - 1
while q:
x, idx = q.pop()
if idx > j or idx < i: continue
ans = max(ans, x*(j-i+1))
if idx > k: j = idx - 1
elif idx < k: i = idx + 1
else: break
return ans
以 nums[k] 为中心向两边扩展找更小的数的作用范围,直到 数组的最小值 作用范围是 整个数组,同时更新答案。
class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
i, j, n = k, k, len(nums)
ans = min_ = nums[k]
while i >= 0 or j < n:
while i >= 0 and nums[i] >= min_: i -= 1
while j < n and nums[j] >= min_: j += 1
# 当前 min_ 作用范围是 [i+1, j-1]
ans = max(ans, min_*(j - i - 1))
if j < n and i >= 0:
min_ = max(nums[i], nums[j])
else:
min_ = nums[i] if j >= n else nums[j]
return ans
*1157.子数组中占绝大多数的元素
1186.删除一次得到子数组最大和
class Solution:
def maximumSum(self, arr: List[int]) -> int:
ans = a = b = -inf
for i, x in enumerate(arr):
b = max(b + x, a) # 删除 x
a = max(a + x, x) # 不删除 x
ans = max(ans, a, b)
return ans
*1330.翻转子数组得到最大的数组值
*1460.通过翻转子数组使两个数组相等
*1526.形成目标数组的子数组最少增加次数
*1546.和为目标值且不重叠的非空子数组的最大数目
1567.乘积为正数的最长子数组长度
class Solution:
def getMaxLen(self, nums: List[int]) -> int:
even, ans, fisrt, left = True, 0, -1, -1 # even 倜数
for i, x in enumerate(nums):
if x == 0:
left, even, fisrt = i, True, -1 # 初始化
continue
if x < 0:
if fisrt == -1:
fisrt = i # 初始化后负数第一次出现的位置
even = not even
if even: ans = max(ans, i - left) # 偶数个负数
else: ans = max(ans, i - fisrt)
return ans
*1569.将子数组重新排序得到同一个二叉查找树的方案数
*1574.删除最短的子数组使剩余数组有序
class Solution:
def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
n = len(arr)
# 左边找第一个小于前一个的元素
for left in range(1, n):
if arr[left] < arr[left - 1]: break
else: return 0
# 右边找第一个大于后一个的元素
for right in range(n - 2, -1, -1):
if arr[right+1] < arr[right]: break
ans = min(n - left, right + 1)
i, j = 0, right + 1
while i < left and j < n:
if arr[i] <= arr[j]:
ans = min(ans, j - i - 1);
i += 1
else: j += 1
return ans
1630.等差子数组
class Solution:
def checkArithmeticSubarrays(self, nums: List[int], l: List[int], r: List[int]) -> List[bool]:
def check(a, b):
x = sorted(nums[a:b+1])
diff = x[1] - x[0]
return all(x[i] - x[i-1] == diff for i in range(2, len(x)))
return [check(a, b) for a, b in zip(l, r)]
1764.通过连接另一个数组的子数组得到一个数组
class Solution:
def canChoose(self, groups: List[List[int]], nums: List[int]) -> bool:
n, i = len(nums), 0
for group in groups:
k = len(group)
while i + k <= n:
if group == nums[i: i + k]:
i += k
break
else: i += 1
else: return False
return True
2261.含最多K个可整除元素的子数组
class Solution:
def countDistinct(self, nums: List[int], k: int, p: int) -> int:
s, n = set(), len(nums)
for i in range(n):
count = 0
for j in range(i, n):
if nums[j] % p == 0: count += 1
if count > k: break
s.add(tuple(nums[i:j+1]))
return len(s)
6248. 统计中位数为 K 的子数组
581.最短无序连续子数组
class Solution:
def findUnsortedSubarray(self, nums: List[int]) -> int:
q, n = [], len(nums)
left, right = len(nums) - 1, 0
mx, mn = nums[0], nums[-1]
for i, x in enumerate(nums):
'''
# 利用单调栈找左边界
while q and x < nums[q[-1]]:
left = min(left, q.pop())
q.append(i)
'''
# 逆序遍历,记录大于当前最小值的下标
y = nums[-i -1]
if y > mn: left = n-i-1
else: mn = y
# 记录小于当前最大值的下标
if x < mx: right = i
else: mx = x
return 0 if right == 0 else right - left + 1
i, j = 0, len(nums) - 1
x = sorted(nums)
while x[i] == nums[i]:
if i == j: return 0
i += 1
while x[j] == nums[j]: j -= 1
return j - i + 1
class Solution {
public int findUnsortedSubarray(int[] nums) {
int n = nums.length, left = n - 1, right = 0, max = nums[0], min = nums[n - 1];
for (int i = 0; i < n; i++){
int x = nums[i], y = nums[n-i-1];
if (y > min) left = n-i-1;
else min = y;
if (x < max) right = i;
else max = x;
}
return right == 0 ? 0 : right - left + 1;
}
}
1574.删除最短的子数组使剩余数组有序
class Solution:
def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
n = len(arr)
for i in range(n - 1):
if arr[i] > arr[i + 1]: break
else: return 0
# i 有效,i + 1 无效
for j in range(n - 1, 0, -1):
if arr[j-1] > arr[j]: break
# 整体删除右端,或左端
ans = min(n - i - 1, j)
k = 0
# 从第一个开始试着接后面的
while k <= i and j < n:
if arr[k] <= arr[j]: # 能接
ans = min(ans, j - k - 1)
k += 1
else: j += 1
return ans