Medium
题目描述
给你一个整数数组 arr 和一个整数值 target。
请你在 arr 中找两个互不重叠的子数组,且它们的和都等于 target。可能会有多种方案,请你返回满足要求的两个子数组长度和的最小值。
请返回满足要求的最小长度和,如果无法找到这样的两个子数组,请返回 -1。
示例 1:
输入:arr = [3,2,2,4,3], target = 3
输出:2
解释:只有两个子数组和为 3 ([3] 和 [3])。它们的长度和为 2。
示例 2:
输入:arr = [7,3,4,7], target = 7
输出:2
解释:尽管我们有 3 个和为 7 的非重叠子数组 ([7], [3,4] 和 [7]),但我们会选择第一个和第三个子数组,因为它们的长度和 2 是最小的。
示例 3:
输入:arr = [4,3,2,6,2,3,4], target = 6
输出:-1
解释:我们只有一个和为 6 的子数组。
提示:
1 <= arr.length <= 10^51 <= arr[i] <= 10001 <= target <= 10^8
解题思路
这道题要求找两个不重叠且和为目标值的子数组,使得它们的长度和最小。
核心思路:
- 使用滑动窗口找出所有和为target的子数组及其位置
- 对于每个分割点i,计算左半部分[0,i-1]和右半部分[i,n-1]中最短子数组的长度
- 枚举所有可能的分割点,找到最小的长度和
具体实现:
- 先用滑动窗口技术找出每个位置开始的最短子数组长度(和为target)
- 构建prefix数组:prefix[i]表示在位置i之前(不包括i)的最短子数组长度
- 构建suffix数组:suffix[i]表示从位置i开始(包括i)的最短子数组长度
- 遍历所有分割点,计算prefix[i] + suffix[i]的最小值
时间优化: 可以在一次遍历中同时维护前缀最小值和计算结果,避免额外的数组存储。
这种方法的关键在于如何高效地找到每个位置对应的最短子数组,以及如何维护前缀和后缀的最小值信息。
代码实现
class Solution {
public:
int minSumOfLengths(vector<int>& arr, int target) {
int n = arr.size();
vector<int> best(n, INT_MAX); // best[i] 表示从位置i开始的最短子数组长度
// 使用滑动窗口找出每个起始位置的最短子数组
int left = 0, sum = 0;
for (int right = 0; right < n; right++) {
sum += arr[right];
while (sum > target && left <= right) {
sum -= arr[left++];
}
if (sum == target) {
best[left] = min(best[left], right - left + 1);
}
}
// 从右向左更新best数组,保存从每个位置开始的最短长度
for (int i = n - 2; i >= 0; i--) {
best[i] = min(best[i], best[i + 1]);
}
int result = INT_MAX;
left = 0, sum = 0;
// 再次使用滑动窗口,对于每个找到的子数组,查看其右侧是否有合适的子数组
for (int right = 0; right < n; right++) {
sum += arr[right];
while (sum > target && left <= right) {
sum -= arr[left++];
}
if (sum == target && right + 1 < n && best[right + 1] != INT_MAX) {
result = min(result, right - left + 1 + best[right + 1]);
}
}
return result == INT_MAX ? -1 : result;
}
};
class Solution:
def minSumOfLengths(self, arr: List[int], target: int) -> int:
n = len(arr)
best = [float('inf')] * n # best[i] 表示从位置i开始的最短子数组长度
# 使用滑动窗口找出每个起始位置的最短子数组
left = 0
current_sum = 0
for right in range(n):
current_sum += arr[right]
while current_sum > target and left <= right:
current_sum -= arr[left]
left += 1
if current_sum == target:
best[left] = min(best[left], right - left + 1)
# 从右向左更新best数组,保存从每个位置开始的最短长度
for i in range(n - 2, -1, -1):
best[i] = min(best[i], best[i + 1])
result = float('inf')
left = 0
current_sum = 0
# 再次使用滑动窗口,对于每个找到的子数组,查看其右侧是否有合适的子数组
for right in range(n):
current_sum += arr[right]
while current_sum > target and left <= right:
current_sum -= arr[left]
left += 1
if current_sum == target and right + 1 < n and best[right + 1] != float('inf'):
result = min(result, right - left + 1 + best[right + 1])
return -1 if result == float('inf') else result
public class Solution {
public int MinSumOfLengths(int[] arr, int target) {
int n = arr.Length;
int[] best = new int[n];
Array.Fill(best, int.MaxValue);
// 使用滑动窗口找出每个起始位置的最短子数组
int left = 0, sum = 0;
for (int right = 0; right < n; right++) {
sum += arr[right];
while (sum > target && left <= right) {
sum -= arr[left++];
}
if (sum == target) {
best[left] = Math.Min(best[left], right - left + 1);
}
}
// 从右向左更新best数组,保存从每个位置开始的最短长度
for (int i = n - 2; i >= 0; i--) {
best[i] = Math.Min(best[i], best[i + 1]);
}
int result = int.MaxValue;
left = 0;
sum = 0;
// 再次使用滑动窗口,对于每个找到的子数组,查看其右侧是否有合适的子数组
for (int right = 0; right < n; right++) {
sum += arr[right];
while (sum > target && left <= right) {
sum -= arr[left++];
}
if (sum == target && right + 1 < n && best[right + 1] != int.MaxValue) {
result = Math.Min(result, right - left + 1 + best[right + 1]);
}
}
return result == int.MaxValue ? -1 : result;
}
}
/**
* @param {number[]} arr
* @param {number} target
* @return {number}
*/
var minSumOfLengths = function(arr, target) {
const n = arr.length;
const leftMin = new Array(n).fill(Infinity);
let left = 0, sum = 0, minLen = Infinity, result = Infinity;
// First pass: find minimum length subarray ending at or before each position
for (let right = 0; right < n; right++) {
sum += arr[right];
while (sum > target) {
sum -= arr[left];
left++;
}
if (sum === target) {
const len = right - left + 1;
minLen = Math.min(minLen, len);
}
leftMin[right] = minLen;
}
// Second pass: find minimum length subarray starting at each position
left = n - 1;
sum = 0;
minLen = Infinity;
for (let right = n - 1; right >= 0; right--) {
sum += arr[right];
while (sum > target) {
sum -= arr[left];
left--;
}
if (sum === target) {
const len = left - right + 1;
minLen = Math.min(minLen, len);
// Check if we can combine with a subarray from the left
if (right > 0 && leftMin[right - 1] !== Infinity) {
result = Math.min(result, len + leftMin[right - 1]);
}
}
}
return result === Infinity ? -1 : result;
};
复杂度分析
| 复杂度类型 | 复杂度大小 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历数组两次,每次都是O(n)的滑动窗口操作 |
| 空间复杂度 | O(n) | 需要额外的best数组来存储每个位置的最短子数组长度 |