Medium
题目描述
给你一个整数数组 nums 和一个整数 k。
返回 nums 的一个子数组的最大和,使得该子数组的长度可以被 k 整除。
示例 1:
输入:nums = [1,2], k = 1
输出:3
解释:
子数组 [1, 2] 的和为 3,长度为 2,可以被 1 整除。
示例 2:
输入:nums = [-1,-2,-3,-4,-5], k = 4
输出:-10
解释:
最大和子数组是 [-1, -2, -3, -4],长度为 4,可以被 4 整除。
示例 3:
输入:nums = [-5,1,2,-3,4], k = 2
输出:4
解释:
最大和子数组是 [1, 2, -3, 4],长度为 4,可以被 2 整除。
提示:
1 <= k <= nums.length <= 2 * 10^5-10^9 <= nums[i] <= 10^9
提示:
- 维护每个可能的
index % k处结束的最小前缀和。
解题思路
这个问题的核心在于找到长度可被 k 整除的子数组中和最大的一个。我们可以使用前缀和的思想来解决。
核心思路:
要使子数组 nums[i...j] 的长度可被 k 整除,需要满足 (j - i + 1) % k == 0,即 j % k == (i - 1) % k。
我们可以计算前缀和,然后对于每个位置 j,我们需要找到最小的前缀和 prefix[i-1],使得 (i-1) % k == j % k。这样 prefix[j] - prefix[i-1] 就是以 j 结尾、长度可被 k 整除的子数组的最大和。
算法步骤:
- 维护一个数组
minPrefix,其中minPrefix[r]表示所有满足index % k == r的位置中的最小前缀和 - 遍历数组,计算前缀和
- 对于每个位置 j,如果
j >= k-1(确保存在长度至少为 k 的子数组),则用当前前缀和减去对应的最小前缀和来更新答案 - 更新
minPrefix数组
这种方法的时间复杂度是 O(n),空间复杂度是 O(k),是最优解法。
代码实现
class Solution {
public:
long long maxSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
vector<long long> minPrefix(k, LLONG_MAX);
minPrefix[0] = 0;
long long prefixSum = 0;
long long maxSum = LLONG_MIN;
for (int i = 0; i < n; i++) {
prefixSum += nums[i];
if (i >= k - 1) {
int remainder = (i + 1) % k;
if (minPrefix[remainder] != LLONG_MAX) {
maxSum = max(maxSum, prefixSum - minPrefix[remainder]);
}
}
int currentRemainder = (i + 1) % k;
minPrefix[currentRemainder] = min(minPrefix[currentRemainder], prefixSum);
}
return maxSum;
}
};
class Solution:
def maxSubarraySum(self, nums: List[int], k: int) -> int:
n = len(nums)
min_prefix = [float('inf')] * k
min_prefix[0] = 0
prefix_sum = 0
max_sum = float('-inf')
for i in range(n):
prefix_sum += nums[i]
if i >= k - 1:
remainder = (i + 1) % k
if min_prefix[remainder] != float('inf'):
max_sum = max(max_sum, prefix_sum - min_prefix[remainder])
current_remainder = (i + 1) % k
min_prefix[current_remainder] = min(min_prefix[current_remainder], prefix_sum)
return max_sum
public class Solution {
public long MaxSubarraySum(int[] nums, int k) {
int n = nums.Length;
long[] minPrefix = new long[k];
for (int i = 0; i < k; i++) {
minPrefix[i] = long.MaxValue;
}
minPrefix[0] = 0;
long prefixSum = 0;
long maxSum = long.MinValue;
for (int i = 0; i < n; i++) {
prefixSum += nums[i];
if (i >= k - 1) {
int remainder = (i + 1) % k;
if (minPrefix[remainder] != long.MaxValue) {
maxSum = Math.Max(maxSum, prefixSum - minPrefix[remainder]);
}
}
int currentRemainder = (i + 1) % k;
minPrefix[currentRemainder] = Math.Min(minPrefix[currentRemainder], prefixSum);
}
return maxSum;
}
}
var maxSubarraySum = function(nums, k) {
const n = nums.length;
const minPrefix = new Array(k).fill(Number.MAX_SAFE_INTEGER);
minPrefix[0] = 0;
let prefixSum = 0;
let maxSum = Number.MIN_SAFE_INTEGER;
for (let i = 0; i < n; i++) {
prefixSum += nums[i];
if (i >= k - 1) {
const remainder = (i + 1) % k;
if (minPrefix[remainder] !== Number.MAX_SAFE_INTEGER) {
maxSum = Math.max(maxSum, prefixSum - minPrefix[remainder]);
}
}
const currentRemainder = (i + 1) % k;
minPrefix[currentRemainder] = Math.min(minPrefix[currentRemainder], prefixSum);
}
return maxSum;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 只需要遍历数组一次,每次操作都是常数时间 |
| 空间复杂度 | O(k) | 需要额外的数组来存储每个余数对应的最小前缀和 |
相关题目
- . Subarray Sums Divisible by K (Medium)