Medium
题目描述
给你一个长度为 n 的整数数组 nums。
选择一个索引 i,使得 0 <= i < n - 1。
对于选定的分割索引 i:
- 设 prefixSum(i) 为 nums[0] + nums[1] + … + nums[i] 的和。
- 设 suffixMin(i) 为 nums[i + 1], nums[i + 2], …, nums[n - 1] 中的最小值。
在索引 i 处分割的得分定义为:
score(i) = prefixSum(i) - suffixMin(i)
返回所有有效分割索引的最大得分。
示例 1:
输入: nums = [10,-1,3,-4,-5]
输出: 17
解释: 最优分割在 i = 2 处,score(2) = prefixSum(2) - suffixMin(2) = (10 + (-1) + 3) - (-5) = 17。
示例 2:
输入: nums = [-7,-5,3]
输出: -2
解释: 最优分割在 i = 0 处,score(0) = prefixSum(0) - suffixMin(0) = (-7) - (-5) = -2。
示例 3:
输入: nums = [1,1]
输出: 0
解释: 唯一有效的分割在 i = 0 处,score(0) = prefixSum(0) - suffixMin(0) = 1 - 1 = 0。
约束:
- 2 <= nums.length <= 10^5
- -10^9 <= nums[i] <= 10^9
解题思路
这道题要求我们找到数组的最优分割点,使得前缀和减去后缀最小值的得分最大。
核心思路:
根据题目提示,我们可以使用前缀和与后缀最小值数组来预处理。对于每个有效分割位置 i(0 <= i < n-1),计算 score(i) = prefixSum(i) - suffixMin(i),然后找到最大得分。
具体步骤:
- 预计算前缀和数组:prefixSum[i] = nums[0] + nums[1] + … + nums[i]
- 预计算后缀最小值数组:suffixMin[i] = min(nums[i], nums[i+1], …, nums[n-1])
- 遍历所有有效分割位置:对于每个 i,计算 prefixSum[i] - suffixMin[i+1],并维护最大值
优化思路:
我们也可以从右往左扫描一遍来计算后缀最小值,从左往右扫描一遍来计算前缀和,在同一次遍历中找到最大得分,这样可以优化空间复杂度。
这种方法的时间复杂度为 O(n),空间复杂度为 O(1)(如果不计算额外数组的话)。
代码实现
class Solution {
public:
long long maximumScore(vector<int>& nums) {
int n = nums.size();
// 计算后缀最小值数组
vector<int> suffixMin(n);
suffixMin[n-1] = nums[n-1];
for (int i = n-2; i >= 0; i--) {
suffixMin[i] = min(nums[i], suffixMin[i+1]);
}
long long maxScore = LLONG_MIN;
long long prefixSum = 0;
// 遍历所有有效分割位置
for (int i = 0; i < n-1; i++) {
prefixSum += nums[i];
long long score = prefixSum - suffixMin[i+1];
maxScore = max(maxScore, score);
}
return maxScore;
}
};
class Solution:
def maximumScore(self, nums: List[int]) -> int:
n = len(nums)
# 计算后缀最小值数组
suffix_min = [0] * n
suffix_min[n-1] = nums[n-1]
for i in range(n-2, -1, -1):
suffix_min[i] = min(nums[i], suffix_min[i+1])
max_score = float('-inf')
prefix_sum = 0
# 遍历所有有效分割位置
for i in range(n-1):
prefix_sum += nums[i]
score = prefix_sum - suffix_min[i+1]
max_score = max(max_score, score)
return max_score
public class Solution {
public long MaximumScore(int[] nums) {
int n = nums.Length;
// 计算后缀最小值数组
int[] suffixMin = new int[n];
suffixMin[n-1] = nums[n-1];
for (int i = n-2; i >= 0; i--) {
suffixMin[i] = Math.Min(nums[i], suffixMin[i+1]);
}
long maxScore = long.MinValue;
long prefixSum = 0;
// 遍历所有有效分割位置
for (int i = 0; i < n-1; i++) {
prefixSum += nums[i];
long score = prefixSum - suffixMin[i+1];
maxScore = Math.Max(maxScore, score);
}
return maxScore;
}
}
var maximumScore = function(nums) {
const n = nums.length;
// 计算后缀最小值数组
const suffixMin = new Array(n);
suffixMin[n-1] = nums[n-1];
for (let i = n-2; i >= 0; i--) {
suffixMin[i] = Math.min(nums[i], suffixMin[i+1]);
}
let maxScore = Number.NEGATIVE_INFINITY;
let prefixSum = 0;
// 遍历所有有效分割位置
for (let i = 0; i < n-1; i++) {
prefixSum += nums[i];
const score = prefixSum - suffixMin[i+1];
maxScore = Math.max(maxScore, score);
}
return maxScore;
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要两次遍历数组,一次计算后缀最小值,一次计算前缀和并求最大得分 |
| 空间复杂度 | O(n) | 需要额外的后缀最小值数组存储空间 |