【华为OD】分割数组的最大差值
题目描述
给定一个由若干整数组成的数组 nums,可以在数组内的任意位置进行分割,将该数组分割成两个非空子数组(即左数组和右数组),分别对子数组求和得到两个值,计算这两个值的差值,请输出所有分割方案中,差值最大的值。
输入描述
- 第一行输入数组中元素个数 n,1 < n <= 100000
- 第二行输入数字序列,以空格进行分隔,数字取值为4字节整数
输出描述
输出差值的最大取值
示例
输入:
6
1 -2 3 4 -9 7
输出:
10
说明:
将数组 nums 划分为两个非空数组的可行方案有:
- 左数组 = [1] 且 右数组 = [-2,3,4,-9,7],和的差值 = |1 - 3| = 2
- 左数组 = [1,-2] 且 右数组 = [3,4,-9,7],和的差值 = |-1-5| = 6
- 左数组 = [1,-2,3,1] 且 右数组 = [4,-9,7],和的差值 = |2 - 2| = 0
- 左数组 = [1,-2,3,4] 且 右数组 = [-9,7],和的差值 = |6 -(-2)| = 8
- 左数组 = [1,-2,3,4,-9] 且 右数组 = [7],和的差值 = |-3-7| = 10
最大的差值为 10
解题思路
这是一个数组分割问题,需要找到最优的分割点使得左右两部分的和的差值最大。
核心思想:
- 对于每个可能的分割点,计算左右两部分的和
- 计算两部分和的绝对差值
- 找出所有差值中的最大值
关键优化:使用前缀和来快速计算任意子数组的和,避免重复计算。
我将提供两种解法:暴力解法 和前缀和优化解法。
解法一:暴力解法
对每个分割点,重新计算左右两部分的和。
Java实现
java
import java.util.*;
public class Solution1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = sc.nextInt();
}
int maxDiff = 0;
// 枚举所有可能的分割点
for (int i = 1; i < n; i++) {
// 计算左部分的和
int leftSum = 0;
for (int j = 0; j < i; j++) {
leftSum += nums[j];
}
// 计算右部分的和
int rightSum = 0;
for (int j = i; j < n; j++) {
rightSum += nums[j];
}
// 计算差值的绝对值
int diff = Math.abs(leftSum - rightSum);
maxDiff = Math.max(maxDiff, diff);
}
System.out.println(maxDiff);
}
}
Python实现
python
def solve_brute_force():
n = int(input())
nums = list(map(int, input().split()))
max_diff = 0
# 枚举所有可能的分割点
for i in range(1, n):
# 计算左部分的和
left_sum = sum(nums[:i])
# 计算右部分的和
right_sum = sum(nums[i:])
# 计算差值的绝对值
diff = abs(left_sum - right_sum)
max_diff = max(max_diff, diff)
print(max_diff)
solve_brute_force()
C++实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
int maxDiff = 0;
// 枚举所有可能的分割点
for (int i = 1; i < n; i++) {
// 计算左部分的和
int leftSum = 0;
for (int j = 0; j < i; j++) {
leftSum += nums[j];
}
// 计算右部分的和
int rightSum = 0;
for (int j = i; j < n; j++) {
rightSum += nums[j];
}
// 计算差值的绝对值
int diff = abs(leftSum - rightSum);
maxDiff = max(maxDiff, diff);
}
cout << maxDiff << endl;
return 0;
}
解法二:前缀和优化解法
使用前缀和数组来快速计算任意子数组的和,避免重复计算。
Java实现
java
import java.util.*;
public class Solution2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = sc.nextInt();
}
// 计算前缀和
long[] prefixSum = new long[n + 1];
for (int i = 0; i < n; i++) {
prefixSum[i + 1] = prefixSum[i] + nums[i];
}
long maxDiff = 0;
// 枚举所有可能的分割点
for (int i = 1; i < n; i++) {
// 左部分和:prefixSum[i] - prefixSum[0] = prefixSum[i]
long leftSum = prefixSum[i];
// 右部分和:prefixSum[n] - prefixSum[i]
long rightSum = prefixSum[n] - prefixSum[i];
// 计算差值的绝对值
long diff = Math.abs(leftSum - rightSum);
maxDiff = Math.max(maxDiff, diff);
}
System.out.println(maxDiff);
}
}
Python实现
python
def solve_prefix_sum():
n = int(input())
nums = list(map(int, input().split()))
# 计算前缀和
prefix_sum = [0] * (n + 1)
for i in range(n):
prefix_sum[i + 1] = prefix_sum[i] + nums[i]
max_diff = 0
# 枚举所有可能的分割点
for i in range(1, n):
# 左部分和:prefix_sum[i]
left_sum = prefix_sum[i]
# 右部分和:prefix_sum[n] - prefix_sum[i]
right_sum = prefix_sum[n] - prefix_sum[i]
# 计算差值的绝对值
diff = abs(left_sum - right_sum)
max_diff = max(max_diff, diff)
print(max_diff)
solve_prefix_sum()
C++实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
// 计算前缀和
vector<long long> prefixSum(n + 1, 0);
for (int i = 0; i < n; i++) {
prefixSum[i + 1] = prefixSum[i] + nums[i];
}
long long maxDiff = 0;
// 枚举所有可能的分割点
for (int i = 1; i < n; i++) {
// 左部分和:prefixSum[i]
long long leftSum = prefixSum[i];
// 右部分和:prefixSum[n] - prefixSum[i]
long long rightSum = prefixSum[n] - prefixSum[i];
// 计算差值的绝对值
long long diff = abs(leftSum - rightSum);
maxDiff = max(maxDiff, diff);
}
cout << maxDiff << endl;
return 0;
}
解法三:数学优化解法(最优解)
通过数学分析,我们可以发现一个重要性质:如果总和为S,左部分和为L,那么右部分和为S-L,差值为|L-(S-L)| = |2L-S|。
Java实现
java
import java.util.*;
public class Solution3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = sc.nextInt();
}
// 计算总和
long totalSum = 0;
for (int num : nums) {
totalSum += num;
}
long maxDiff = 0;
long leftSum = 0;
// 枚举所有可能的分割点
for (int i = 0; i < n - 1; i++) {
leftSum += nums[i];
// 差值 = |2 * leftSum - totalSum|
long diff = Math.abs(2 * leftSum - totalSum);
maxDiff = Math.max(maxDiff, diff);
}
System.out.println(maxDiff);
}
}
Python实现
python
def solve_optimized():
n = int(input())
nums = list(map(int, input().split()))
# 计算总和
total_sum = sum(nums)
max_diff = 0
left_sum = 0
# 枚举所有可能的分割点
for i in range(n - 1):
left_sum += nums[i]
# 差值 = |2 * left_sum - total_sum|
diff = abs(2 * left_sum - total_sum)
max_diff = max(max_diff, diff)
print(max_diff)
solve_optimized()
C++实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
// 计算总和
long long totalSum = 0;
for (int num : nums) {
totalSum += num;
}
long long maxDiff = 0;
long long leftSum = 0;
// 枚举所有可能的分割点
for (int i = 0; i < n - 1; i++) {
leftSum += nums[i];
// 差值 = |2 * leftSum - totalSum|
long long diff = abs(2 * leftSum - totalSum);
maxDiff = max(maxDiff, diff);
}
cout << maxDiff << endl;
return 0;
}
算法复杂度分析
解法一:暴力解法
- 时间复杂度:O(N²),对每个分割点都要重新计算左右两部分的和
- 空间复杂度:O(1)
解法二:前缀和优化解法
- 时间复杂度:O(N),预处理前缀和O(N),枚举分割点O(N)
- 空间复杂度:O(N),存储前缀和数组
解法三:数学优化解法
- 时间复杂度:O(N),只需要一次遍历
- 空间复杂度:O(1),只使用常数额外空间
示例分析
对于给定的示例:[1, -2, 3, 4, -9, 7]
总和 = 1 + (-2) + 3 + 4 + (-9) + 7 = 4
各个分割点的情况:
- 分割点1:左=[1],右=[-2,3,4,-9,7],左和=1,右和=3,差值=|1-3|=2
- 分割点2:左=[1,-2],右=[3,4,-9,7],左和=-1,右和=5,差值=|-1-5|=6
- 分割点3:左=[1,-2,3],右=[4,-9,7],左和=2,右和=2,差值=|2-2|=0
- 分割点4:左=[1,-2,3,4],右=[-9,7],左和=6,右和=-2,差值=|6-(-2)|=8
- 分割点5:左=[1,-2,3,4,-9],右=[7],左和=-3,右和=7,差值=|-3-7|=10
最大差值为10。
使用数学优化公式验证:
- 分割点5:leftSum = -3,|2×(-3) - 4| = |-6-4| = 10 ✓
总结
三种解法各有特点:
- 暴力解法:思路直观,但时间复杂度高,适合小规模数据
- 前缀和优化:经典的优化思路,时间复杂度降为O(N),但需要额外空间
- 数学优化:最优解法,通过数学分析得出公式,时间复杂度O(N),空间复杂度O(1)
对于这道题目,由于N可达100000,推荐使用数学优化解法 ,它不仅时间效率最高,空间效率也最优。关键洞察是发现差值公式:|2 × leftSum - totalSum|
,这样就避免了重复计算右部分的和。