LeetCode 2574. 左右元素和的差值
一、题目含义
给定一个数组 nums,对于数组中的每个位置 i:
- 左侧和
leftSum[i]:nums[i]左边所有元素的和(不包含nums[i]本身) - 右侧和
rightSum[i]:nums[i]右边所有元素的和(不包含nums[i]本身)
要求返回一个数组 answer,其中 answer[i] = |leftSum[i] - rightSum[i]|。
边界情况
i = 0时,左边没有元素,leftSum[0] = 0i = n-1时,右边没有元素,rightSum[n-1] = 0
示例
输入:nums = [10, 4, 8, 3]
逐步计算:
| 位置 i | numsi | 左侧元素 | leftSum | 右侧元素 | rightSum | |leftSum - rightSum| |
|---|---|---|---|---|---|---|
| 0 | 10 | (无) | 0 | 4, 8, 3 | 15 | |0 - 15| = 15 |
| 1 | 4 | 10 | 10 | 8, 3 | 11 | |10 - 11| = 1 |
| 2 | 8 | 10, 4 | 14 | 3 | 3 | |14 - 3| = 11 |
| 3 | 3 | 10, 4, 8 | 22 | (无) | 0 | |22 - 0| = 22 |
输出:[15, 1, 11, 22]
二、解题思路
暴力做法(不推荐)
对每个位置 i,分别向左遍历求和、向右遍历求和。每个位置耗时 O(n),总共 O(n²),数据量大时会超时。
前缀和优化(推荐)
核心观察 :leftSum 和 rightSum 都可以用递推的方式计算,不需要每次从头求和。
第一步:从左往右算 leftSum
leftSum[0] = 0 // 左边没有元素
leftSum[i] = leftSum[i-1] + nums[i-1] // 在前一个结果上累加
为什么可以这样?因为 leftSum[i-1] 已经是 nums[0] + nums[1] + ... + nums[i-2],现在再加上 nums[i-1],恰好就是 nums[i] 左边所有元素的和。
以示例为例:
leftSum[0] = 0
leftSum[1] = 0 + 10 = 10
leftSum[2] = 10 + 4 = 14
leftSum[3] = 14 + 8 = 22
第二步:从右往左算 rightSum
rightSum[n-1] = 0 // 右边没有元素
rightSum[i] = rightSum[i+1] + nums[i+1] // 在后一个结果上累加
逻辑完全对称,方向相反:
rightSum[3] = 0
rightSum[2] = 0 + 3 = 3
rightSum[1] = 3 + 8 = 11
rightSum[0] = 11 + 4 = 15
第三步:求差的绝对值
answer[i] = |leftSum[i] - rightSum[i]|
复杂度分析
| 指标 | 值 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
三趟遍历,每趟 O(n),总时间 O(n)。额外使用了 left 和 right 两个数组。
三、代码逐行解读
cpp
class Solution {
public:
vector<int> leftRightDifference(vector<int>& nums) {
vector<int> ret(nums.size()); // 存放最终答案
vector<int> left(nums.size()), right(nums.size()); // 左侧和、右侧和
// 第一步:从左往右,递推计算 leftSum
for (int i = 0; i < nums.size(); i++) {
if (i == 0) {
left[0] = 0; // 最左边,左侧无元素
} else {
left[i] = left[i - 1] + nums[i - 1]; // 递推:前一个左侧和 + 前一个元素
}
}
// 第二步:从右往左,递推计算 rightSum
for (int i = nums.size() - 1; i >= 0; i--) {
if (i == nums.size() - 1) {
right[i] = 0; // 最右边,右侧无元素
} else {
right[i] = right[i + 1] + nums[i + 1]; // 递推:后一个右侧和 + 后一个元素
}
}
// 第三步:计算每个位置的差的绝对值
for (int i = 0; i < nums.size(); i++) {
ret[i] = abs(left[i] - right[i]);
}
return ret;
}
};
四、进阶优化:空间压缩
上面的代码用了 left 和 right 两个额外数组。实际上可以只用一个数组,把额外空间从 O(n) 降到 O(1)。
思路:用一个变量代替 rightSum 数组
- 先从左往右填好
left数组 - 再从右往左遍历,用一个变量
rightSum记录右侧累加和,边遍历边算答案
cpp
class Solution {
public:
vector<int> leftRightDifference(vector<int>& nums) {
int n = nums.size();
vector<int> left(n);
// 计算前缀和
for (int i = 1; i < n; i++) {
left[i] = left[i - 1] + nums[i - 1];
}
// 从右往左,用变量维护 rightSum
int rightSum = 0;
for (int i = n - 1; i >= 0; i--) {
left[i] = abs(left[i] - rightSum); // 直接在 left 上覆盖为答案
rightSum += nums[i]; // 更新右侧和(给下一轮 i-1 用)
}
return left;
}
};
理解要点 :
rightSum初始为 0(最右边元素的右侧和就是 0),每往左走一步,就把当前元素加进去,因为对下一个位置来说,当前元素就是"右侧元素"之一。
复杂度对比
| 版本 | 时间 | 额外空间 |
|---|---|---|
| 基础版本 | O(n) | O(n) |
| 空间压缩版本 | O(n) | O(1)* |
*不计返回值占用的空间
五、总结
| 要点 | 说明 |
|---|---|
| 问题本质 | 求每个位置左右两侧元素和的差的绝对值 |
| 核心技巧 | 前缀和递推,避免重复求和 |
| 方向 | leftSum 从左往右递推,rightSum 从右往左递推 |
| 边界 | 最左侧 leftSum=0,最右侧 rightSum=0 |
| 可优化 | 用变量代替 rightSum 数组,省掉 O(n) 额外空间 |