跳到主要内容

2025. Partitions Count

Maximum Number of Ways to Partition an Array

非常烧脑的一题 我其实前八次尝试全错😆

那时候对于明显复杂的题目

比如通过率才36%左右的2025题

很容易陷入乱枪打鸟

我后来才懂得踩刹车 冷静观察再动手

Max Partitions Count的出厂值

题目有说 我们能选择什么元素都不动

首先就拿原始那长度nn的数组来看

有多少个1i<n1 \leq i < n的索引ii做到

sum(nums[:i])=sum(nums[i:])sum(nums[:i]) = sum(nums[i:])

因此Max Partitions Count的出厂值

便是C=Σi=1n11(sum(nums[:i])=sum(nums[i:]))C = \Sigma_{i = 1}^{n - 1} 1(sum(nums[:i]) = sum(nums[i:]))

难在能去改某一索引上的元素

然而题目还有说 我们也能选择 刚好一个 索引jj

把索引jj上的元素改成输入的变量kk

言下之意 便是我们得看看究竟哪个jj能够

C=maxjΣi=1n11(sum(nums[:i])=sum(nums[i:]))C' = max_{j} \Sigma_{i = 1}^{n - 1} 1(sum(nums'[:i]) = sum(nums'[i:]))

numsnums'numsnums唯一的区别就在nums[j]=knums'[j] = k

最后再回传max(C,C)max(C, C')作为答案

寻找C'

前后缀差值的概念

首先储备一个叫做rightDiffCounts的哈希表

还有叫做pivotIdx的索引 题目有定义

pivotIdx必须从1开始 且小于nn

因此pivotIdx沿著1一路访问到n1n - 1的路上

要计算 前后缀差值 D=D = sum(nums[:pivotIdx]) - sum(nums[pivotIdx:])

并在rightDiffCounts纪录该DD出现的次数

这前后缀差值有什么用呢?想像一下

要是 某个小于pivotIdx的索引ll 被选中

nums[l]nums[l]被改成了kk 而且nums[l]k=Dnums[l] - k = D

这样岂不让sum(nums[:pivotIdx]) - sum(nums[pivotIdx:])变成零?

pivotIdx因这么改而成为合法的分割点 C'增加1

左右对称的陷阱

不过还得再留意到一件事

就是某个ll索引 其nums[l]nums[l]变成kk

能因此成为合法分割点的索引 可能在ll左 可能在ll

光靠rightDiffCounts只纪录分割点在ll右 造成的前后缀差值

仍须leftDiffCounts管理分割点在ll左 造成的前后缀差值

最终计数的遍历流程

前缀视角

负责遍历的索引ll0l<n0 \leq l < n

每次都去计算看看 当nums[l]nums[l]被改成kk

ll右边有多少个前后缀差值为nums[l]knums[l] - k的分割点

nums[l]nums[l]的更动 ll右边分割点视角 属前缀和变化

计数靠rightDiffCountsnums[l]knums[l] - k即可

后缀视角

相似的对称性来看 nums[l]nums[l]更动成kk

对于ll左边的潜在分割点视角 属于后缀和变化

得统计ll左边有多少个前后缀差值为knums[l]k - nums[l]的分割点

计数靠leftDiffCountsknums[l]k - nums[l]即可

因此nums[l]nums[l]改成kk 能造出的完美分割点数量就是

ClC'_l = leftDiffCounts[k - nums[l]] + rightDiffCounts[nums[l] - k]

藉此和已知的历史最大值CC比较 企图更新CC

这边主要得注意的就是 因为是前缀与后缀

查询的差值正好差一个负号

随时更新两张哈希表

ll被访问完后 算出Dl=sum(nums[:l+1])sum(nums[l+1:])D_l = \text{sum}(nums[:l + 1]) - \text{sum}(nums[l + 1:])

DlD_lrightDiffCounts的计数减1

DlD_lleftDiffCounts的计数加1

因为对于比ll大的所有索引 它们的视角来讲

ll只可能做左方分割点 不可能做右方的

#include <unordered_map>
#include <vector>
using namespace std;

int countMaxPartitions(vector<int>& nums, int k) // LeetCode Q.2025.
{
long long totalSum = 0; // Prevents overflow.
for (const auto& num : nums)
totalSum += num;

// Left diff: only consider potential pivot indices at left side.
// Right diff: only consider potential pivot indices at right side.
// Map key: diff = prefix sum - suffix sum.
unordered_map<long long, int> leftDiffCounts, rightDiffCounts;

// Default to natural value: leave entire array unchanged.
int maxPartitionWays = 0;

long long prefixSum = 0;
for (int pivotIdx = 1; pivotIdx < nums.size(); pivotIdx++) {
prefixSum += nums[pivotIdx - 1];
long long suffixSum = totalSum - prefixSum;
rightDiffCounts[prefixSum - suffixSum]++;

// Born as a great partition. No need to change.
if (prefixSum == suffixSum)
maxPartitionWays++;
}

prefixSum = 0; // Reset to let next for loop sweep again.

for (int idx = 0; idx < nums.size(); idx++) {
int num = nums[idx];
prefixSum += num;

int partitionWays = 0;

// Potential pivot idx is at the right of current idx.
partitionWays += rightDiffCounts[num - k];

// Potential pivot idx is at the left of current idx.
partitionWays += leftDiffCounts[k - num];

if (partitionWays > maxPartitionWays)
maxPartitionWays = partitionWays;

long long diff = 2 * prefixSum - totalSum; // Prefix - suffix.

// For future indices, this diff is only available as left side.
leftDiffCounts[diff]++;
rightDiffCounts[diff]--;
}

return maxPartitionWays;
}

Prefix_Sum Efficiency 时间复杂度O(n)O(n) 空间也是O(n)O(n)

这题逻辑确实蛮复杂的 会需要想好几遍才抓到命脉