给你一个整数数组 nums 。一个子数组 [ n u m s l , n u m s l + 1 , . . . , n u m s r − 1 , n u m s r ] [nums_l, nums_{l+1}, ..., nums_{r-1}, nums_{r}] [numsl,numsl+1,...,numsr−1,numsr] 的 和的绝对值 为 a b s ( n u m s l + n u m s l + 1 + . . . + n u m s r − 1 + n u m s r ) abs(nums_l + nums_{l+1} + ... + nums_{r-1} + nums_{r}) abs(numsl+numsl+1+...+numsr−1+numsr) 。
请你找出 n u m s nums nums 中 和的绝对值 最大的任意子数组(可能为空),并返回该 最大值 。
abs(x) 定义如下:
如果 x 是负整数,那么 a b s ( x ) = − x abs(x) = -x abs(x)=−x 。
如果 x 是非负整数,那么 a b s ( x ) = x abs(x) = x abs(x)=x 。
1 < = n u m s . l e n g t h < = 1 0 5 − 1 0 4 < = n u m s [ i ] < = 1 0 4 1 <= nums.length <= 10^5\\ -10^4 <= nums[i] <= 10^4 1<=nums.length<=105−104<=nums[i]<=104
暴力
最简单的方法就是枚举出所有可能的子数组,计算其和的绝对值,然后取max。但是子数组的数量规模是 N 2 N^2 N2,所以暴力会TLE,而且即使计算出了子数组,计算其和也是需要时间的。
因为最终需要知道子数组的和的绝对值的最大值。
要得到这样的最大值,那么子数组的和sum一定要尽可能的大,或者尽可能的小,即最大的正数或者最小的负数。
因此只需要在数组中找到子数组和最大的 s u m > = 0 sum>=0 sum>=0,或者 s u m < 0 sum<0 sum<0,sum的最小负数。
到这里,就和某个问题很像了
可以利用前缀和的思路,进行累加 s u m sum sum,然后与之前最小的 p r e m i n premin premin, s u m − p r e m i n sum-premin sum−premin,此时 s u m − p r e m i n sum-premin sum−premin,就是可能的正数的最大子数组和。
同样的 s u m − p r e m a x sum- premax sum−premax,就是可能的负数的最小子数组和。
public int maxAbsoluteSum(int[] nums) {
int n = nums.length, ans = nums[0];
int premax = 0,premin = 0;
int sum = 0 ;
for(int i = 0;i<n;i++){
sum += nums[i];
int a = sum - premin; // 非负数的最大
int b = premax -sum;//负数的绝对值最大
ans = Math.max(ans,Math.max(b,a));
premin = Math.min(sum,premin);
premax = Math.max(sum,premax);
}
return ans;
}
时间复杂度 O ( N ) O(N) O(N)
空间复杂度 O ( 1 ) O(1) O(1)
public int maxAbsoluteSum(int[] nums) {
int s = 0, mx = 0, mn = 0;
for (int x : nums) {
s += x;
// mx = Math.max(mx, s);
// mn = Math.min(mn, s);
if (s > mx) mx = s;
else if (s < mn) mn = s; // 效率更高的写法
}
return mx - mn;
}
时间复杂度 O ( N ) O(N) O(N)
空间复杂度 O ( 1 ) O(1) O(1)
灵神的代码更精简,不过他是从前缀和的另一个角度来看这个问题的,所以有点不一样。
Array
Presum