【华为OD】分割数组的最大差值

【华为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

解题思路

这是一个数组分割问题,需要找到最优的分割点使得左右两部分的和的差值最大。

核心思想:

  1. 对于每个可能的分割点,计算左右两部分的和
  2. 计算两部分和的绝对差值
  3. 找出所有差值中的最大值

关键优化:使用前缀和来快速计算任意子数组的和,避免重复计算。

我将提供两种解法:暴力解法前缀和优化解法

解法一:暴力解法

对每个分割点,重新计算左右两部分的和。

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:左=[1],右=[-2,3,4,-9,7],左和=1,右和=3,差值=|1-3|=2
  2. 分割点2:左=[1,-2],右=[3,4,-9,7],左和=-1,右和=5,差值=|-1-5|=6
  3. 分割点3:左=[1,-2,3],右=[4,-9,7],左和=2,右和=2,差值=|2-2|=0
  4. 分割点4:左=[1,-2,3,4],右=[-9,7],左和=6,右和=-2,差值=|6-(-2)|=8
  5. 分割点5:左=[1,-2,3,4,-9],右=[7],左和=-3,右和=7,差值=|-3-7|=10

最大差值为10。

使用数学优化公式验证:

  • 分割点5:leftSum = -3,|2×(-3) - 4| = |-6-4| = 10 ✓

总结

三种解法各有特点:

  1. 暴力解法:思路直观,但时间复杂度高,适合小规模数据
  2. 前缀和优化:经典的优化思路,时间复杂度降为O(N),但需要额外空间
  3. 数学优化:最优解法,通过数学分析得出公式,时间复杂度O(N),空间复杂度O(1)

对于这道题目,由于N可达100000,推荐使用数学优化解法 ,它不仅时间效率最高,空间效率也最优。关键洞察是发现差值公式:|2 × leftSum - totalSum|,这样就避免了重复计算右部分的和。

相关推荐
weixin_307779132 小时前
最小曲面问题的欧拉-拉格朗日方程 / 曲面极值问题的变分法推导
算法
RTC老炮2 小时前
webrtc弱网-AlrDetector类源码分析与算法原理
服务器·网络·算法·php·webrtc
孤廖2 小时前
【算法磨剑:用 C++ 思考的艺术・Dijkstra 实战】弱化版 vs 标准版模板,洛谷 P3371/P4779 双题精讲
java·开发语言·c++·程序人生·算法·贪心算法·启发式算法
sali-tec2 小时前
C# 基于halcon的视觉工作流-章33-矩状测量
开发语言·人工智能·算法·计算机视觉·c#
凯子坚持 c3 小时前
Redis 核心数据结构:String 类型深度解析与 C++ 实战
数据结构·c++·redis
songx_993 小时前
leetcode29( 有效的括号)
java·数据结构·算法·leetcode
于樱花森上飞舞3 小时前
【java】常见排序算法详解
java·算法·排序算法
GawynKing3 小时前
图论3 图的遍历
算法·深度优先
东方芷兰5 小时前
Leetcode 刷题记录 21 —— 技巧
java·算法·leetcode·职场和发展·github·idea