子数组(Subarray)

Subarray

一、前缀和

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
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值