题目描述

✅ 1013. 将数组分成和相等的三个部分

image-20260928235434770

题意分析

将整个数组切成三个连续、非空的部分,使三段元素和相等,判断能否做到。每个元素必须属于其中一段,不能重排或跳过元素;只需要返回是否可行,不需要输出切点。

数组可以包含正数、负数和零。若总和不能被三整除,必定无法划分;若能整除,每一段的目标和就确定为 total / 3。找到前两段后,剩余部分的和虽然自动等于目标,仍须保证它至少有一个元素。

解法:贪心确定前两个分段

核心思路

[!blue]

从左向右累加当前段的和,第一次等于目标时就结束第一段;清零后,从下一项开始寻找第二段。找到两段后,总和减去这两段便得到第三段的和,因此无需再单独寻找第三段的结束位置。

为什么第一段可以取最早的切点?假设存在一种合法划分,第一段结束在更靠后的位置。最早切点之前的和与这个合法第一段的和都等于目标,所以两个切点之间的额外部分和为零。把这段零和部分留给第二段,不会改变第二段的和,原来的第二个切点仍然可用。因此提前结束第一段不会破坏原本存在的可行方案。

第一段确定后,第二段也取最早达到目标的位置。它不会晚于任何可行的第二切点;如果原来存在非空第三段,提前结束第二段仍然会留下非空后缀。总和守恒又保证这段后缀的和等于目标,所以这次贪心选择同样安全。

代码只扫描到倒数第二个元素,专门为第三段保留至少一个位置。每段都在读入至少一个元素后才能计数,即使目标为零,也不会把空段算作完成。找到第二段立即返回真;扫描结束仍找不到两段则返回假。

这个证明依赖相同前缀和之间的差为零,不依赖段和随扫描单调变化。因此允许负数,当前段和暂时超过目标也不能提前判失败。

解题步骤

  1. 扫描数组求总和,不可被三整除时返回 false。
  2. 令目标和为总和的三分之一,当前段和、已完成段数都从零开始。
  3. 从下标 0 扫描到 n - 2,不断加入当前元素。
  4. 当前段和等于目标时,将段数加一并清零段和;段数达到二时返回 true。
  5. 循环结束后仍未找到两段,返回 false。

代码实现

class Solution {
    public boolean canThreePartsEqualSum(int[] arr) {
        long total = 0;

        for (int num : arr) {
            total += num;
        }

        if (total % 3 != 0) {
            return false;
        }

        long target = total / 3;
        long sum = 0;
        int parts = 0;

        // 最后一个元素不作切点,保证第三段非空
        for (int i = 0; i < arr.length - 1; i++) {
            sum += arr[i];

            if (sum == target) {
                parts++;
                // 已完成当前段,下一项开始重新累计
                sum = 0;

                if (parts == 2) {
                    return true;
                }
            }
        }

        return false;
    }
}
func canThreePartsEqualSum(arr []int) bool {
    var total int64
    for _, num := range arr {
        total += int64(num)
    }
    if total%3 != 0 {
        return false
    }

    target := total / 3
    var sum int64
    parts := 0
    // 最后一个元素不作切点,保证第三段非空
    for i := 0; i < len(arr)-1; i++ {
        sum += int64(arr[i])
        if sum == target {
            parts++
            // 已完成当前段,下一项开始重新累计
            sum = 0
            if parts == 2 {
                return true
            }
        }
    }
    return false
}

复杂度分析

  • 时间复杂度:$O(n)$,总和与寻找切点各扫描一次数组。
  • 空间复杂度:$O(1)$,只保存总和、目标、当前段和及段数。

关键点总结

[!green]

  • 只需确定两刀,第三段的目标和由总和自动保证,非空性由扫描上界保证。
  • 最早切点与更晚的可行切点之间和为零,因此提前切分不会破坏后续可行性。
  • 每个新段从下一个元素重新累加,目标为零时也能保证三段都非空。

易错点总结

[!yellow]

  • 扫描包括最后一个元素,可能在数组末尾完成第二段,错误地留下空第三段。
  • 总和可被三整除只是必要条件,还要找到两个合法切点,不能直接返回真。
  • 找到一段就返回,还没有完成第二刀,也不能保证剩余两段能够平分。
  • 完成一段后不清零段和,会把后续判断误变成前缀和判断,与当前状态定义不一致。
  • 当前和超过目标时就停止,会漏掉后续负数抵消后重新达到目标的情况。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/00181600
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!