目录

题目描述

724. 寻找数组的中心下标

题意分析

找一个下标 i,使得它左边所有元素之和等于它右边所有元素之和nums[i] 自己不算进任何一边)。存在多个时返回最靠左的那个,不存在则返回 -1。

要什么:一个下标,而且是最小的那个。所以扫描方向必须是从左到右,且一命中就立刻返回——不能先把所有候选收集起来再取最小,那是白做功。

定义里有两处必须抠死的细节。第一,nums[i] 本身两边都不属于,它是被排除在外的分割点,不是归给某一侧。第二,最左端和最右端也是合法候选:i = 0 时左边是空区间,和为 0;i = n - 1 时右边是空区间,和同样为 0。空区间的和是 0 而不是「无效」,这一点让端点无需任何特判。

约束透露的信号:元素可以是负数,长度上万。允许负数是这道题最重要的信号,它直接封死了所有依赖单调性的做法——前缀和不再随下标递增,双指针无法通过「和太小就右移」来收缩,二分更是无从谈起。剩下能用的只有「一次线性扫描 + 常数状态」这一条路。数据规模只有 $10^4$,$O(n^2)$ 勉强能过,但这题存在的意义就是练前缀和的等式变形,写成平方级等于没做。

边界:数组长度至少为 1;长度为 1 时唯一的下标 0 左右都是空区间,答案恒为 0;总和可能为负;元素值在 $\pm 1000$ 以内、长度 $10^4$,总和最大 $10^7$,int 完全够用,不必上 long

解法:先算总和再左右累计

核心思路

先看暴力:对每个下标 i,分别用一层循环累加左边和右边,比较是否相等。时间 $O(n^2)$。瓶颈在于相邻两个下标的左侧和只差一个元素,却每次都从头重算——i 处的左侧和是 i-1 处的左侧和加上 nums[i-1],这个增量关系被完全浪费了。

顺着增量关系走,自然想到边扫边维护 leftSum。但右侧和怎么办?如果也想边扫边维护一个 rightSum,就得先知道全部元素之和才能倒推,那还不如直接把这个「全部之和」求出来。于是得到本解法的两趟结构:第一趟只做一件事——求出总和 total;第二趟从左到右扫描,用 leftSumtotal 现场算出右侧和

关键恒等式是:把数组按下标 i 切成三段,左段、nums[i]、右段,三者之和必然等于 total,因此

\[\text{rightSum}(i) = \text{total} - \text{leftSum}(i) - nums[i]\]

判定条件随之变成一行:

\[\text{leftSum} == \text{total} - \text{leftSum} - nums[i]\]

于是右侧和根本不需要单独维护,$O(1)$ 就能从已有的两个量导出。

本解法的循环不变量是:在进入第 i 轮循环体的那一刻,leftSum 恰好等于 nums[0..i-1] 的和(i = 0 时为空区间,值为 0)。维持这个不变量的手段是「先判定,后累加」:判定用的 leftSum 必须是不含 nums[i] 的那个值,判定完成后再执行 leftSum += nums[i],为下一轮做好准备。

有了这个不变量,两个端点都自动成立:i = 0leftSum = 0 正是空左区间的和;i = n - 1 时判定式右侧退化成 total - leftSum - nums[n-1] = 0,正是空右区间的和。一个特判都不用写。

顺带说明为什么等式要写成 leftSum == total - leftSum - nums[i] 而不是化简成 2 * leftSum + nums[i] == total——两者数学上等价,后者甚至少一次运算;但前者的每一项都有直白的物理含义(左边是左侧和,右边是右侧和),读代码的人一眼就能对上题意,而化简式需要在脑子里再推一次。可读性在面试白板上比省一次减法重要。

解题步骤

  • 第一趟遍历累加出 total。为什么必须是独立的一趟:右侧和的计算依赖全局信息,而扫描到 i 时后面的元素还没被看过;只有先拿到总和,才能在第二趟里用减法反推出右侧和。
  • leftSum 初始化为 0。为什么是 0 而不是 nums[0]:不变量要求 leftSum 在第 i 轮开始时等于前 i 个元素之和,i = 0 时前 0 个元素之和就是 0;写成 nums[0] 会让下标 0 这个候选被跳过。
  • 第二趟从 i = 0 扫到 n - 1,先做判定 leftSum == total - leftSum - nums[i]。为什么判定必须排在累加之前:nums[i] 不属于左侧,一旦提前累加进 leftSum,判定式的两边就都错了——左边多算了 nums[i],右边少减了它。
  • 命中立刻 return i。为什么可以立刻返回:扫描顺序天然从小到大,第一个满足条件的下标就是最小的那个,继续扫下去只会找到更大的候选。
  • 未命中则 leftSum += nums[i],进入下一轮。为什么这样能维持不变量:进入第 i+1 轮时,leftSum 恰好包含了 nums[0..i],正是前 i+1 个元素之和。
  • 循环结束返回 -1。为什么不能返回 0 或抛异常:题面明确规定不存在中心下标时返回 -1,而 0 是一个合法下标值,用它表示「不存在」会与真实答案混淆。

具体用例 nums = [1, 7, 3, 6, 5, 6] 走一遍,预期答案是 3。

第一趟total = 1 + 7 + 3 + 6 + 5 + 6 = 28

第二趟leftSum 初始为 0:
i = 0nums[0] = 1):左侧和 leftSum = 0(空区间),右侧和 28 - 0 - 1 = 27,即 7+3+6+5+6 = 27,对得上。0 != 27,不命中。累加后 leftSum = 1
i = 1nums[1] = 7):左侧和 1,右侧和 28 - 1 - 7 = 20,即 3+6+5+6 = 20,对得上。1 != 20,不命中。累加后 leftSum = 8
i = 2nums[2] = 3):左侧和 8(即 1+7),右侧和 28 - 8 - 3 = 17,即 6+5+6 = 178 != 17,不命中。累加后 leftSum = 11
i = 3nums[3] = 6):左侧和 11(即 1+7+3),右侧和 28 - 11 - 6 = 11,即 5+6 = 11两边相等,命中,返回 3

再走一个体现边界的用例 nums = [2, 1, -1],预期答案是 0。
total = 2 + 1 + (-1) = 2
i = 0leftSum = 0,右侧和 2 - 0 - 2 = 0,即 1 + (-1) = 0相等,返回 0。这个例子同时展示了两件事:左端点的空区间和 0 是合法的,以及负数让右侧和可以「反向抵消」——任何依赖前缀和单调递增的思路在这里都会失效。

最后走一个无解用例 nums = [1, 2, 3]total = 6i = 00 vs 6-0-1=5,不等,leftSum = 1i = 11 vs 6-1-2=3,不等,leftSum = 3i = 23 vs 6-3-3=0,不等。循环结束,返回 -1。

代码实现

class Solution {
    // 直接算左右和容易重复,先求总和后按下标动态维护左侧和,右侧和可 O(1) 得出。
    public int pivotIndex(int[] nums) {
        int total = 0;
        for (int num : nums) {
            total += num;
        }

        int leftSum = 0;
        for (int i = 0; i < nums.length; i++) {
            if (leftSum == total - leftSum - nums[i]) {
                return i;
            }
            leftSum += nums[i];
        }

        return -1;
    }
}
func pivotIndex(nums []int) int {
    // 直接算左右和容易重复,先求总和后按下标动态维护左侧和,右侧和可 O(1) 得出。
    total := 0
    for _, num := range nums {
        total += num
    }

    leftSum := 0
    for i, num := range nums {
        if leftSum == total-leftSum-num {
            return i
        }
        leftSum += num
    }

    return -1
}

复杂度分析

  • 时间复杂度:$O(n)$。凭什么:两趟独立的线性遍历,第一趟求和、第二趟判定,每个元素各被访问常数次,循环体内只有加减和比较;提前返回只会让实际访问更少。相比暴力的 $O(n^2)$,省下的正是「相邻下标之间重复累加」的那部分。
  • 空间复杂度:$O(1)$。凭什么:只用了 totalleftSumi 三个标量,没有开前缀和数组。虽然这题的思路来自前缀和,但因为只需要「当前位置的前缀和」这一个值,用一个变量滚动即可,不必物化整张前缀和表——这是前缀和类题目里最常见的一次空间优化。

关键点总结

  • 「左段 + 当前元素 + 右段 = 总和」是一条可以随手写出的恒等式,它让右侧和不必单独维护。凡是需要同时掌握「某点左右两侧聚合值」的题目,先求全局总量再用减法反推,通常比维护两个方向的累积量更省事也更不易错。
  • 循环不变量要精确到「进入循环体那一刻」。这里是「leftSum 等于 nums[0..i-1] 之和」,它直接决定了累加语句必须放在判定之后。写这类边扫边累的代码时,先在纸上写清不变量,语句顺序就不用猜了。
  • 让空区间的和等于 0,端点就不需要特判。把「空」定义成加法的单位元,是让主逻辑自然覆盖边界的通用技巧;反之若把端点当成特殊情况单独处理,代码会立刻变长且容易漏。
  • 元素可能为负,是排除单调性做法的硬信号。看到「整数」而不是「正整数」时,要主动放弃双指针收缩、前缀和二分这类依赖单调性的思路。
  • 求「最左的满足条件者」就用从左到右扫 + 立即返回,不要收集全部候选再取最小;这既省时间,也让「多解时返回哪个」这条要求从代码结构上被保证,而不是靠事后挑选。
  • 表达式的可读性优先于省一次运算leftSum == total - leftSum - nums[i] 比等价的 2 * leftSum + nums[i] == total 多一次减法,但前者的两边分别就是左侧和与右侧和,白板上讲解时一句话说清;后者要现推。
  • 面试视角:这题本身不难,面试官真正想看的是你能否把「两侧和相等」这个条件干净地转成一个 $O(1)$ 可判定的等式,以及能否主动说清端点为什么不用特判。常见追问是「如果要返回所有中心下标呢」(去掉提前返回,改为收集到列表)和「如果数组会被频繁修改并多次查询呢」(这时应改用树状数组或线段树维护前缀和,单次修改与查询都是 $O(\log n)$)。能顺势聊到后者,说明你理解前缀和「静态查询快、动态更新慢」的本质局限。

易错点总结

  • 错误写法:先 leftSum += nums[i] 再做判定 → 用例 nums = [1,7,3,6,5,6]i = 3leftSum 已变成 17,判定式右侧算成 28 - 17 - 6 = 517 != 5 不命中;后续也不会命中,最终返回 -1,而正确答案是 3。
  • 错误写法:判定式写成 leftSum == total - leftSum(忘了减 nums[i] → 用例 nums = [1,7,3,6,5,6],等价于把 nums[i] 划归右侧;i = 3 时右侧算成 17 而非 11,不命中,整轮扫描无解返回 -1。题面明确规定中心元素两边都不算。
  • 错误写法:leftSum 初始化为 nums[0] → 用例 nums = [2,1,-1],下标 0 这个正确答案在第一轮就被错误的 leftSum = 2 挡掉(2 vs 2-2-2=-2),继续扫描后返回 -1。
  • 错误写法:循环从 i = 1 开始,认为端点不可能是答案 → 用例 nums = [1],唯一合法下标 0 被跳过,返回 -1;正确答案是 0,因为左右都是空区间、和都为 0。
  • 错误写法:循环到 i < n - 1 为止,认为最后一个下标不可能是答案 → 用例 nums = [1,-1,0]total = 0;下标 0 不满足(左 0、右 -1+0 = -1),下标 1 不满足(左 1、右 0),下标 2 满足(左 1+(-1) = 0、右为空区间和 0),正确答案是 2;不扫到末位就会漏解返回 -1。
  • 错误写法:找到后不返回而是记录下来继续扫,最后返回最后一次记录的下标 → 用例 nums = [0,0,0],三个下标全部满足,正确答案是最小的 0,该写法返回 2。
  • 错误写法:无解时返回 0 → 用例 nums = [1,2,3],不存在中心下标,返回 0 会被误解成「下标 0 是答案」。哨兵值必须选一个不可能是合法答案的数,这里题面指定为 -1。
  • 错误写法:用双指针从两端向中间收缩,哪边小就移哪边 → 用例 nums = [2,-1,-1,2],前缀和随负数上下波动,「和小就右移」的收缩规则不再保证不漏解,指针会停在错误位置。允许负数时任何依赖单调性的收缩都不成立。
  • 错误写法:预先构造完整的前缀和数组 pre[i+1] = pre[i] + nums[i],再用 pre[i]pre[n] - pre[i+1] 比较,但下标写成 pre[n] - pre[i] → 用例 nums = [1,7,3,6,5,6]pre[n] - pre[i] 包含了 nums[i] 自己,右侧和被高估,i = 3 处算成 17 而非 11,漏掉正确答案。用前缀和数组时,右侧和必须从 i + 1 开始取。
  • 错误写法:把 totalleftSum 声明为 long 之后返回类型也顺手改动,或反过来担心溢出而全程用 long → 本题元素绝对值不超过 1000、长度不超过 $10^4$,总和上界 $10^7$,int 绰绰有余;无谓的类型放大不会出错但会掩盖你对数据范围的判断,面试中被问到「为什么用 long」却答不出具体上界是减分项。

相似题目

题目 难度 考察点
LCR 012. 寻找数组的中心下标 简单 与本题同题,可直接套用同一份代码
303. 区域和检索 - 数组不可变 简单 需要多次任意区间查询,必须真的把前缀和数组物化下来而非用单个变量滚动
238. 除了自身以外数组的乘积 中等 把加法换成乘法且禁用除法,只能左右各扫一遍累积,无法用「总积除以自身」反推
560. 和为 K 的子数组 中等 前缀和配哈希表统计出现次数,求的是子数组个数而非分割点,含负数时不能用滑窗
974. 和可被 K 整除的子数组 中等 前缀和取模后按同余类计数,难点在负数取模要归一化到非负
1013. 将数组分成和相等的三个部分 简单 从一个分割点变成两个,需先判断总和能否被 3 整除,再贪心地找前两段边界
1732. 找到最高海拔 简单 前缀和的最基础形态,边累加边取最大值,起点 0 必须计入候选
1685. 有序数组中差绝对值之和 中等 同样靠「总和减左侧和」拆解每个位置的贡献,但要利用有序性去掉绝对值符号