合并区间形成目标数组的最小操作次数
字数 19495 2025-12-10 14:22:30

合并区间形成目标数组的最小操作次数

题目描述
给定一个初始数组 arr,其中每个元素都是整数,以及一个目标数组 target,长度与 arr 相同。你可以对 arr 执行任意次数的操作:每次操作可以选择 arr 中一个连续的子数组,并将该子数组中的所有元素合并(即用它们的和替换这个子数组)。合并后,子数组的长度变为1,其值为原子数组元素的和。操作会改变数组的长度,最终需要将 arr 转化为与 target 完全相同的数组(长度和每个位置的元素都相等)。问:从初始 arr 变换到目标 target最小操作次数是多少?如果无法变换,则返回 -1。

示例
初始数组: arr = [1, 2, 3, 4]
目标数组: target = [3, 7]
一种方案:

  1. 合并子数组 [1, 2] 得到 [3, 3, 4]
  2. 合并子数组 [3, 4] 得到 [3, 7]
    操作次数 = 2。
    输出: 2

解题思路
这道题本质是:我们有一系列数字,每次可以将一个连续区间合并为一个数(区间和),最终要得到目标数组。这类似于“通过合并相邻元素来匹配目标序列”。


第一步:问题转化与可行性判断
首先,无论怎么合并,数组元素的总和不会变。因此,如果 sum(arr) != sum(target),直接返回 -1。
其次,目标数组 target 可以看作是将 arr 划分成若干段,每段合并后的和恰好等于 target 中对应位置的值。例如,arr = [1,2,3,4],target = [3,7],就是把 arr 分成两段:[1,2] 和 [3,4],每段和分别为 3 和 7。
所以,问题转化为:能否将 arr 划分成若干连续段,使得第 i 段的元素和等于 target[i],并且我们要求的是合并操作的最小次数。
注意:每次操作合并一个连续子数组,该子数组内可以包含多个数,合并后子数组变成1个数。
如果某一段长度是 L,那么要将这一段合并成1个数,需要 L-1 次操作(每次合并减少一个元素,直到只剩一个数)。
但是,如果我们可以在一次操作中直接合并整个段吗?题目允许合并任意连续子数组,所以一次操作可以把一个段(长度 L)直接合并为1个数。
那么,对于一段长度为 L 的子数组,我们只需要 1 次操作就可以把它合并成1个数。不过,如果我们分多次合并(比如先合并其中一部分,再合并其他部分),可能操作次数更多,显然不是最优。因此,对于每一段,我们总是用一次操作把它合并成一个数。
那么,总的操作次数 = 段数(因为每个段一次操作)。但这里有一个陷阱:初始时 arr 长度是 n,目标是长度 m,我们要合并成 m 段,那么需要减少 (n - m) 个元素。每次操作可以减少 (选择的子数组长度 - 1) 个元素。
如果我们一次合并的区间长度是 L,那么减少的元素数是 L-1。
设我们最终划分成了 k 段(k = len(target)),那么总减少元素数 = n - k。
假设我们对第 i 段(长度 L_i)进行一次合并操作,这次操作减少的元素数为 L_i - 1。
总操作次数 = 合并次数,并且总减少元素数 = Σ (L_i - 1) = (Σ L_i) - k = n - k。
所以,无论我们如何安排合并,只要我们把数组划分成 k 段,并且每段合并一次,总操作次数就是 k 次吗?不对,因为 k 是目标长度,也就是最终段数。我们要做的合并次数 = 初始段数 - 最终段数 + ??? 等等,我们仔细推导:
初始有 n 个数,最终有 k 个数。每次合并操作将连续若干个数字合并成1个数字,所以一次操作会减少 (len-1) 个数字。设操作次数为 x,每次操作减少的数字数为 r_i,则总减少数字数 = Σ r_i = n - k。
因为每次操作减少数字数至少为 1(当合并长度为2时,减少1个数字),最多为 n-1(一次性合并整个数组)。
如果我们想最小化操作次数 x,那么我们希望每次操作减少尽可能多的数字,即每次合并尽可能长的连续段。
但合并的段必须对应目标数组的一段,也就是说,每次合并的区间必须是一个“完整的目标段”,否则合并后得到的数与目标不对应。
所以,我们必须先把 arr 划分成与 target 对应的段,然后每个段单独合并一次。
那么,每个段只需要一次操作(合并整个段),总操作次数 = 段数 k。
但是,有没有可能通过跨段合并来减少操作次数?不可以,因为跨段合并会使得段的和不符合 target 的某个数,除非跨的多个段的和正好等于 target 的某个数,但这样 target 的定义就变了,因为 target 已经固定了每个位置的数。
实际上,我们必须保证最终数组的每个位置恰好是 target[i],所以最终数组的第 i 个数一定是 arr 中某连续几个数的和,且这个和等于 target[i]。因此,arr 必须能划分成若干连续子数组,每个子数组和等于 target 的对应元素。
如果这种划分存在,那么每个子数组(长度 L_i)我们只需一次操作合并它。所以总操作次数 = 目标数组长度 m。
然而,这是正确的吗?让我们检查示例:arr=[1,2,3,4], target=[3,7],m=2,计算得到 2 次操作,与示例一致。
再试一个例子:arr=[1,1,1,1], target=[2,2],划分成[1,1]和[1,1],各需一次合并,总操作=2。
但如果我们先合并前两个1得到[2,1,1],再合并后两个1得到[2,2],操作次数=2,与直接每段一次操作相同。
再考虑 arr=[1,2,3], target=[6],划分成整个数组[1,2,3]和为6,一次操作即可。m=1,操作=1。
所以看起来总操作次数 = len(target)。
但等等,题目要求的是最小操作次数,如果某一段长度已经是1,我们还需要合并吗?不需要,因为已经是一个数了。
所以,对于长度已经是1的段(即 arr 中某个单独的数正好等于 target 的某个数),我们不需要操作。
那么,设划分后,第 i 段的长度是 L_i,如果 L_i = 1,则不需要操作;如果 L_i > 1,则需要 1 次操作。
总操作次数 = 满足 L_i > 1 的段的数量。
这样,我们要最小化操作次数,就要尽可能让划分出的段中,长度=1的段尽可能多。
但划分是固定的吗?不,因为 arr 划分成若干段,每段和等于 target[i],但可能有多种划分方式(如果 arr 中有0,可能影响分段长度,但元素都是正整数?题目没说,假设都是正整数)。
如果都是正整数,那么和相等的连续子数组划分是唯一的(因为前缀和严格递增)。
所以,如果 arr 和 target 的元素都是正整数,那么划分方式唯一。我们只需检查能否划分,然后数一下长度>1的段数即可。
但题目没有说元素为正整数,可能是任意整数,包括0。那么,如果包含0,则划分可能不唯一,因为0不影响和,但可以合并到左边或右边段,从而改变段长度。
这时,我们可以通过选择划分,使得更多段长度为1,从而减少操作次数。
这就是问题的难点:我们需要找到一种划分方式,使得“长度>1的段”的数量最小化。


第二步:动态规划状态定义
设 n = len(arr), m = len(target)。
我们定义 dp[i] 表示考虑 arr 的前 i 个元素,匹配了 target 的前 j 个元素时的最小操作次数?不对,应该是同时遍历 arr 和 target。
我们定义 dp[i][j] 表示:使用 arr 的前 i 个元素(即 arr[0..i-1]),匹配 target 的前 j 个元素(即 target[0..j-1])的最小操作次数。但这样状态转移复杂,因为 i 和 j 不是同步增长的。
另一种思路:由于我们必须将 arr 划分成 m 段,每段和等于 target[j],我们可以用指针在 arr 上移动,同时用 j 表示当前 target 段索引。
定义 dp[j] 表示匹配到 target 的第 j 段时,所用的 arr 的最小前缀长度,以及对应的最小操作次数?这样不好。
更好的做法:区间DP,但这里 arr 是固定的,我们只需要在 arr 上找若干分割点,使得每段和等于 target 对应值。
设目标有 m 段,我们需要在 arr 中找到 m-1 个分割点,0 = p0 < p1 < p2 < ... < p_{m-1} < p_m = n,使得 sum(arr[p_{k-1} .. p_k - 1]) = target[k-1] 对每个 k 成立。
如果有多组分割点,我们要选择一种,使得“长度>1的段”的数量最少。
因为 target 的和与 arr 总和相等,所以分割点必须满足前缀和相等条件。
定义 arr 的前缀和数组 pre,pre[0]=0, pre[i]=sum(arr[0..i-1])。
定义 target 的前缀和 tpre,tpre[0]=0, tpre[j]=sum(target[0..j-1])。
那么,第 j 段对应的 arr 区间 [l, r) 必须满足 pre[r] - pre[l] = target[j]。
且 tpre[j] = pre[r]。
所以,分割点 r 必须满足 pre[r] 在 tpre 中出现,并且 pre[r] = tpre[j] 对于某个 j。
那么,我们可以将 tpre 的值映射到下标 j。
我们从左到右扫描 arr 前缀和,当 pre[i] = tpre[j] 时,说明我们可以完成前 j 段,且最后一段的右端点是 i。
那么,设 f[j] 表示匹配完前 j 段(即 target[0..j-1]),且最后一段以 i 结尾(pre[i]=tpre[j])时的最小操作次数。
但 i 是什么?我们其实不关心具体的 i,只关心最后一个段的长度。
可以这样:dp[j] 表示匹配完前 j 段的最小操作次数,且我们知道此时 arr 用到前缀 pre 的某个位置 i,满足 pre[i]=tpre[j]。
状态转移:dp[j] = min_{0 <= k < j} (dp[k] + cost(k, j)),其中 cost(k, j) 表示将 arr 中对应 target 的第 k 段到第 j-1 段(即 target[k..j-1])划分出来,这一大段(对应 arr 的一个区间)如果长度大于1,则需要1次操作,否则0次。但注意,我们并不需要一次操作合并整个大段,因为我们可以对其中每个小段分别操作。
实际上,我们是将 arr 划分成 m 段,每段对应一个 target 元素。如果我们把相邻的若干个小段看作一个整体,但这样合并后得到的和不是 target 的单个元素,所以不行。我们必须保证每个 target 元素对应 arr 的一个连续子数组,不能合并多个 target 元素对应的段。
所以,每个 target 元素单独对应一段 arr,不可合并多个 target 元素。
那么,cost(k, j) 实际上只针对一段,即 j 是单个段。
所以,dp 的转移应该是:dp[j] = dp[j-1] + (L_j > 1 ? 1 : 0),其中 L_j 是 arr 中对应 target[j-1] 的段的长度。
但 L_j 可能不唯一,因为 arr 中可能有多个区间和等于 target[j-1]。我们需要选择长度最长的还是最短的?我们要最小化操作次数,所以如果该段长度=1,则操作+0;如果长度>1,则操作+1。我们希望长度=1的段尽可能多,所以对于给定的 target[j-1],我们希望找到 arr 中和等于它的区间,且长度=1的区间优先。
但区间是连续的,且必须按顺序匹配 target。
所以,我们从左到右匹配 target 的每个元素,在 arr 中寻找一段区间,其和等于 target[j],且紧接着上一段的结束位置。
如果存在多个结束位置(因为 arr 中有0,可以延长区间而不改变和),我们可以选择结束位置最靠左的(即长度最短的),这样更可能让后面的段也有机会长度为1吗?不一定。
因此,我们需要搜索所有可能的分段方式,找到最少数量的长度>1的段。
这是一个典型的序列分割DP。
定义 dp[i] 表示匹配完 target 的前 i 个元素时,所用的 arr 的最小操作次数,且此时 arr 的结束位置是某个 idx(我们记下这个 idx)。
转移:dp[i] = min_{最后一段的开始位置 s} (dp[i-1] + (最后一段长度>1 ? 1 : 0)),其中最后一段是 arr[s..e],且 sum = target[i-1],且 s 等于上一段的结束位置。
所以,我们需要知道从 arr 的某个位置 start 开始,和为 target[i] 的区间结束位置。
如果 arr 元素非负,则对于固定的 start,和为 target[i] 的结束位置是唯一的(如果存在),因为前缀和单调不减。
如果 arr 元素有正有负,则可能有多个结束位置。
我们可以预处理出每个 start 和每个 target 值对应的所有可能结束位置。
但 n 和 m 可达 1000 左右,所以 O(n*m) 可行。


第三步:状态转移方程
设 dp[i] 表示匹配完 target 的前 i 个元素时的最小操作次数,且此时 arr 的结束位置是 pos[i](即最后一段的右边界+1)。
初始化 dp[0]=0, pos[0]=0。
对于每个 i 从 1 到 m:
我们需要找到从 pos[i-1] 开始的一段连续子数组,其和等于 target[i-1]。
设从 start = pos[i-1] 开始,我们尝试结束位置 end,使得 sum(arr[start..end]) = target[i-1]。
如果找到,则 pos[i] = end+1,且 dp[i] = dp[i-1] + (end-start+1 > 1 ? 1 : 0)。
但可能有多个 end 满足条件(如果 arr 有负数或0),我们要选择使总操作数最小的那个。
所以,dp[i] = min_{end} (dp[i-1] + (len>1?1:0)),其中 len = end-start+1,且 sum(arr[start..end])=target[i-1]。
同时记录 pos[i] = end+1。
但 pos[i] 会随着 end 变化,不同的 end 会影响后面的匹配。
所以,我们实际上需要二维状态:dp[i][e] 表示匹配前 i 个 target 元素,且最后一个 target 元素对应 arr 的结束位置是 e 时的最小操作次数。
转移:dp[i][e] = min_{s} (dp[i-1][s] + (e-s > 0 ? 1 : 0)),其中 s 是上一个结束位置+1,即 s = prev_end+1,且 sum(arr[s..e]) = target[i-1]。
但 prev_end 是上一个匹配的结束位置,即对于 dp[i-1][s'] 中的 s' 其实是上一个结束位置+1?我们调整一下。
设 f[i][j] 表示匹配前 i 个 target 元素,且第 i 个元素对应 arr 的区间结束于位置 j(即 arr 的索引 j,从0开始)的最小操作次数。
则第 i 个元素对应的区间是 [k+1, j],其中 k 是上一个结束位置。
条件:sum(arr[k+1 .. j]) = target[i-1]。
转移:f[i][j] = min_{k < j} (f[i-1][k] + (j-k > 1 ? 1 : 0)),其中 sum(arr[k+1..j]) = target[i-1]。
初始 f[0][-1] = 0,我们设 k=-1 表示开始前。
最终答案 = min_{j} f[m][j],其中 j = n-1 必须成立,因为要用完 arr 所有元素。
复杂度 O(m * n^2) 太高,需要优化。
注意,sum(arr[k+1..j]) 可以用前缀和快速计算,即 pre[j+1]-pre[k+1]。
条件 pre[j+1]-pre[k+1] = target[i-1] => pre[k+1] = pre[j+1] - target[i-1]。
所以,对于固定的 j 和 i,我们需要找到所有 k 满足 k < j 且 pre[k+1] = pre[j+1] - target[i-1]。
我们可以用哈希表记录每个前缀和值对应的所有位置下标(指的是 pre 数组的下标,即前缀个数)。
但 k+1 的范围是 1..n,我们记 pos_sum[val] 为所有下标 t 使得 pre[t] = val。
那么,对于每个 j,我们计算 needed = pre[j+1] - target[i-1],然后在 pos_sum[needed] 中找到所有 t 满足 t-1 < j,即 t <= j,且 t >= i(因为至少要 i 个元素?其实不需要,只要 t>0)。
但我们需要 t-1 是上一个结束位置,即第 i-1 段结束于 t-1。
所以,f[i][j] = min_{t in pos_sum[needed], t-1 < j} (f[i-1][t-1] + (j - (t-1) > 1 ? 1 : 0))。
其中 len = j - (t-1) = j - t + 1。
所以,f[i][j] = min_{t} (f[i-1][t-1] + (j-t+1 > 1 ? 1 : 0))。
即 f[i][j] = min_{t} (f[i-1][t-1] + (j-t >= 1 ? 1 : 0))。
如果 j==t-1,则区间长度为0,不可能,因为区间和为正?除非 target[i-1]=0 且区间长度0,但区间长度0时和为0,所以如果 target[i-1]=0,我们可以取空区间,但题目要求合并操作,区间长度至少1?实际上区间必须至少1个元素,因为合并需要至少1个元素。但 target 元素为0时,arr 中对应段和=0,可以是一段0,长度任意。
暂时假设 target 元素非零,则区间长度至少1。
那么,j-t+1 >=1 => j>=t。
而 t <= j 且 t-1 < j 恒成立。
所以,我们只需要 t <= j 且 t 在 pos_sum[needed] 中。
这样,我们可以用前缀最小值优化:
对于每个 i,我们遍历 j 从 0 到 n-1,对于每个 j,需要 needed = pre[j+1] - target[i-1]。
然后遍历所有 t 满足 pre[t] = needed 且 t <= j。
在遍历过程中,我们维护两个最小值:
min0 = min_{t} f[i-1][t-1] (当区间长度=1,即 j-t+1=1 => j=t)
min1 = min_{t} f[i-1][t-1] + 1 (当区间长度>1,即 j>=t+1)
但区间长度=1 对应 j = t,区间长度>1 对应 j >= t+1。
所以,对于固定的 j,我们考虑所有 t <= j,如果 j==t,则候选值 = f[i-1][t-1];否则候选值 = f[i-1][t-1]+1。
我们可以分别维护两个数组:
best0[j] = 对于所有 t 满足 pre[t]=needed 且 t<=j,当 j==t 时的 f[i-1][t-1] 的最小值。
best1[j] = 对于所有 t 满足 pre[t]=needed 且 t<=j,当 j>t 时的 f[i-1][t-1] 的最小值。
但 t 和 j 有关,不好直接维护。
我们可以换一种方式:先遍历 t,再更新对应的 j。
对于每个 t,我们需要更新所有 j >= t 且 pre[j+1] = pre[t] + target[i-1]。
设 val = pre[t] + target[i-1]。
我们找到所有 j 满足 pre[j+1] = val 且 j >= t。
对于这些 j,如果 j==t,则区间长度=1,用 f[i-1][t-1] 更新 f[i][j];否则用 f[i-1][t-1]+1 更新。
这样,我们可以用两个数组 min_val0 和 min_val1 来记录,但 j 是离散的。
实际上,我们可以对每个 val,记录所有 j 的位置,然后对于每个 t,我们找到所有 j 的位置,进行更新。
但这样复杂度可能 O(n^2),但 n<=1000 可以接受。

更简单的实现:直接用 O(mn^2) 的 DP,n,m<=100 可以,但题目可能 n,m<=1000,需要优化。
假设 n,m<=1000,O(m
n^2)=1e9 太大。
我们需要优化到 O(mn) 或 O(n^2)。
注意到,对于每个 i,我们只关心 f[i-1][
],所以可以滚动数组。
主要瓶颈是对于每个 (i,j),要枚举 t。
我们可以用哈希表记录每个前缀和值对应的 t 列表,然后对于每个 j,我们遍历所有 t 满足 pre[t] = needed 且 t<=j。
如果每个前缀和值对应的 t 很多,最坏 O(n),则总 O(mn^2)。
但我们可以优化:对于每个 i 和每个 needed 值,我们按 t 递增顺序遍历,维护 f[i-1][t-1] 的最小值。
具体地,对于每个 i,我们遍历 j 从 0 到 n-1,同时维护一个哈希表 map,其中 key=needed,value=到目前为止对于该 needed 的 min(f[i-1][t-1]),其中 t 满足 pre[t]=needed 且 t<=当前考虑的某个值。
但我们需要区分 j==t 和 j>t 的情况。
我们可以维护两个值:min_same 和 min_diff,其中 min_same 对应 j==t 的情况,min_diff 对应 j>t 的情况。
实际上,我们可以这样做:
对于每个 needed 值,我们维护一个最小值 min_val,即所有 t 对应的 f[i-1][t-1] 的最小值。
然后,对于每个 j,我们计算 needed,然后 f[i][j] = min_val + 1,除非存在 t 使得 j==t,此时可以用 f[i-1][t-1] 更新(不加1)。
所以,我们需要知道是否存在 t 满足 pre[t]=needed 且 t=j。
我们可以提前建立前缀和到位置列表的映射。
然后,对于每个 j,我们查看 needed 是否等于 pre[j]?注意 needed = pre[j+1] - target[i-1]。
我们令 t 满足 pre[t]=needed,且 t 是整数 1..n。
如果存在 t 使得 t = j+1?因为 t 是 pre 数组下标,对应 arr 的前 t 个元素的和。t 和 j 的关系:区间是 [t, j] 对应 arr 的下标从 t 到 j(0-based),和为 pre[j+1]-pre[t]。
我们设 start = t,则区间为 [start, j],和为 pre[j+1]-pre[start] = target[i-1]。
所以 needed = pre[start] = pre[j+1] - target[i-1]。
所以,当 start = j+1 时,区间长度为0,不可能,除非 target[i-1]=0。
区间长度=1 对应 start = j,即区间 [j, j]。
所以,当 start = j 时,区间长度=1。此时 needed = pre[j] = pre[j+1] - target[i-1]。
所以,我们需要检查是否存在 start 使得 pre[start]=needed 且 start = j。
即 needed 是否等于 pre[j]。
因此,对于每个 j,我们计算 needed = pre[j+1] - target[i-1]。
如果 pre[j] == needed,则存在一个区间长度=1 的情况,此时 f[i][j] 可以用 f[i-1][j-1] 更新(不加1)。
同时,对于其他 start < j,我们可以用 min_{start < j} f[i-1][start-1] + 1 更新。
所以,我们维护一个数组 minF[start-1] 的最小值,其中 start 满足 pre[start] = needed。
我们可以用哈希表 map[needed] = 目前为止所有 start 对应的 f[i-1][start-1] 的最小值。
那么,对于每个 j,我们计算 needed,然后:
cand1 = map[needed] + 1 // 对应 start < j
cand2 = 如果 pre[j] == needed,则 f[i-1][j-1]
f[i][j] = min(cand1, cand2)
但注意,start 可以等于 j,此时就是 cand2 的情况,我们单独处理。
而 map[needed] 应该包含所有 start <= j 的情况吗?不,我们只需要 start < j 的情况,因为 start=j 单独处理。
所以,我们需要在遍历 j 时,动态更新 map:当遍历到 j 时,我们先计算 f[i][j],然后再将 start = j+1 的情况加入 map?不对,因为 start 是下一个区间的开始,而我们现在计算的是 f[i][j],其中 j 是当前区间的结束。
实际上,对于当前 i,我们需要的是上一个状态 f[i-1][start-1],其中 start 是当前区间的开始。
我们可以在遍历 j 之前,先把所有 start 对应的 f[i-1][start-1] 按 pre[start] 分组,得到每个 needed 对应的最小值。
但 needed 依赖于 j,因为 needed = pre[j+1] - target[i-1],所以对于不同的 j,needed 不同。
所以,我们需要对于每个 needed 值,维护 f[i-1][start-1] 的最小值,其中 pre[start] = needed。
然后对于每个 j,我们计算 needed,查询 map[needed] 得到 min_{start} f[i-1][start-1],但这里的 start 可以任意,我们需要 start <= j 吗?需要,因为区间 [start, j] 要求 start <= j。
所以,我们需要的是 start <= j 的那些 start 的最小值。
那么,我们可以按 j 递增顺序遍历,同时维护每个 needed 对应的最小值,随着 j 增加,我们将 start = j 的情况加入 map(注意,start 是下一个区间的开始,所以当 j 增加时,我们可以将 start = j 对应的 f[i-1][j-1] 加入 map[pre[j]])。
具体步骤:
初始化 map 为空。
对于 j 从 0 到 n-1:
1. 计算 needed = pre[j+1] - target[i-1]。
2. 如果 map 中存在 needed,则 cand1 = map[needed] + 1,否则 cand1 = INF。
3. 如果 pre[j] == needed,则 cand2 = f[i-1][j-1](当 j>0),否则 cand2 = INF。
4. f[i][j] = min(cand1, cand2)。
5. 更新 map[pre[j+1]] = min(map[pre[j+1]], f[i-1][j])。注意,这里 pre[j+1] 是下一个区间的 start 对应的前缀和,因为 start 对应 pre[start],而 start = j+1 时,pre[start] = pre[j+1]。
注意,f[i-1][j] 是前 i-1 段匹配完且结束于 j 的最小操作数。
这样,我们在计算 f[i][j] 时,map[needed] 中存储的是所有 start <= j 的 f[i-1][start-1] 的最小值吗?
因为我们是在计算完 f[i][j] 后才将 f[i-1][j] 加入 map,而 map 的 key 是 pre[start] = pre[j+1],这个 start 是 j+1,大于 j,所以对于后面的 j' > j,这个 start 是 <= j' 的。
因此,对于每个 needed,map[needed] 存储的是所有 start <= 当前 j 的 f[i-1][start-1] 的最小值。
但注意,start 是当前区间的开始,start 的范围是 1..n。
我们在遍历 j 时,逐步将 start = j+1 的情况加入 map,所以当计算 f[i][j] 时,map 中包含的是 start <= j 的情况吗?不,因为 start = j+1 是大于 j 的,我们加入的是下一个 start,所以对于当前的 j,map 中包含的是 start <= j 的情况吗?
我们加入的是 f[i-1][j],对应的 start = j+1,这个 start 是大于 j 的。
所以,实际上,对于当前的 j,map 中包含的是 start <= j 的情况吗?我们需要仔细分析。
假设 j=0,计算 f[i][0] 时,map 是空的,然后我们将 f[i-1][0] 加入 map,对应的 start=1。
当 j=1 时,map 中包含 start=1 的情况,而 start=1 <=1,所以对于 j=1,map 中包含 start=1 的情况,即 start <= j 的情况。
所以,确实,当我们计算 f[i][j] 时,map 中包含的是所有 start <= j 的 f[i-1][start-1] 的最小值。
因为我们在上一步 j-1 时将 start = j 的情况加入了 map。
所以,这个算法正确。
复杂度:对于每个 i,遍历 j 从 0 到 n-1,每次 O(1) 查询和更新 map。
总复杂度 O(m
n)。


第四步:边界条件与初始化
初始化 f[0][-1] = 0,但数组下标没有 -1,我们可以用 f[0][j] 表示匹配0个 target 元素,且结束于 j 的最小操作数,只有 j=-1 是合法的,但我们可以用 f[0][j] 表示匹配0个元素且结束于 j,但此时必须 j=-1,所以我们用 f[0][j] 表示匹配0个元素且结束于 j 是不可能的,设为 INF,除了 f[0][-1]=0。
实现时,我们让下标从0开始,用 f[i][j] 表示匹配前 i 个元素,且结束于 j(0-based)的最小操作数。
我们增加一个虚拟位置 -1,用 f[i][j] 的 j 从 0 到 n-1。
初始化 f[0][j] 全部 INF,但我们可以用 f[0][-1]=0,然后从 i=1 开始,j 从 0 到 n-1 计算。
在计算 f[i][j] 时,如果 j=0,则 cand2 需要 f[i-1][-1],我们可以用 prev0 表示 f[i-1][-1],即上一段结束于 -1 的最小操作数。
我们可以在循环外维护一个变量 prev0 表示 f[i-1][-1]。
对于 i=1,prev0 = f[0][-1] = 0。
然后,对于每个 i,我们遍历 j 从 0 到 n-1,更新 f[i][j],同时维护 map。
最终,答案 = f[m][n-1],如果为 INF 则返回 -1。


第五步:示例推演
arr = [1,2,3,4], target = [3,7]
pre = [0,1,3,6,10]
target 前缀和 tpre=[0,3,10]
m=2, n=4。
初始化 f[0][-1]=0。
i=1, target[0]=3。
初始化 map 为空。
j=0: needed = pre[1]-3=1-3=-2,map 中无,cand1=INF。 pre[0]=0,needed=-2,不等,cand2=INF。 f[1][0]=INF。
更新 map[pre[1]=1] = min(INF, f[0][0]),但 f[0][0] 不存在,我们只考虑 f[0][-1],但 f[0][j] 对于 j>=0 都是 INF,所以 map[1]=INF。
j=1: needed=pre[2]-3=3-3=0,map[0] 不存在,cand1=INF。 pre[1]=1,needed=0,不等,cand2=INF。 f[1][1]=INF。
更新 map[pre[2]=3] = min(INF, f[0][1]) = INF。
j=2: needed=pre[3]-3=6-3=3,map[3] 不存在,cand1=INF。 pre[2]=3,needed=3,相等,cand2 = f[0][1] = INF。 f[1][2]=INF。
更新 map[pre[3]=6] = INF。
j=3: needed=pre[4]-3=10-3=7,map[7] 不存在,cand1=INF。 pre[3]=6,不等于7,cand2=INF。 f[1][3]=INF。
更新 map[pre[4]=10]=INF。
发现 f[1][] 全部 INF,说明无法匹配第一个 target 元素?但我们知道应该是可以的。
错误在哪里?
needed = pre[j+1] - target[i-1]。
当 i=1, j=1: needed=pre[2]-3=3-3=0。
但 pre[0]=0,所以 needed=0 对应 start=0,即区间 [0,1] 的和为 pre[2]-pre[0]=3-0=3,匹配 target[0]=3。
此时 start=0,我们需要 f[i-1][start-1] = f[0][-1] = 0。
但我们的算法中,map[needed] 存储的是 f[i-1][start-1],其中 start 是当前区间的开始,而 start 对应 pre[start]。
当 needed=0,我们需要 map[0] 包含 f[0][-1],但 map[0] 在 j=1 时还未被加入,因为我们在 j=0 时加入的是 pre[1]=1,不是 pre[0]=0。
我们需要在开始遍历 j 之前,先将 start=0 的情况加入 map,即 f[i-1][-1] 对应 pre[0]=0。
所以,初始化 map[pre[0]=0] = f[i-1][-1] = 0。
然后遍历 j 从 0 到 n-1。
重新计算:
i=1, target[0]=3。
初始化 map = {0:0}。
j=0: needed=pre[1]-3=1-3=-2,不在 map,cand1=INF。 pre[0]=0,needed=-2,不等,cand2=INF。 f[1][0]=INF。
更新 map[pre[1]=1] = min(INF, f[0][0]=INF) = INF。
j=1: needed=pre[2]-3=3-3=0,map[0]=0,cand1=0+1=1。 pre[1]=1,needed=0,不等,cand2=INF。 f[1][1]=1。
更新 map[pre[2]=3] = min(INF, f[0][1]=INF) = INF。
j=2: needed=pre[3]-3=6-3=3,map[3]=INF,cand1=INF。 pre[2]=3,needed=3,相等,cand2=f[0][1]=INF。 f[1][2]=INF。
更新 map[pre[3]=6] = INF。
j=3: needed=pre[4]-3=10-3=7,map[7] 无,cand1=INF。 pre[3]=6,不等,cand2=INF。 f[1][3]=INF。
所以 f[1][1]=1,表示匹配第一个 target 元素,结束于 j=1,操作数=1,区间长度=2>1,所以加1。
i=2, target[1]=7。
初始化 map = {0:0}?注意,每轮 i 需要重新初始化 map,包含 f[i-1][-1] 对应 pre[0]=0。
f[i-1][-1] 是 f[1][-1],但 f[1][-1] 未定义,我们只考虑结束于具体位置的情况。
实际上,对于 i=2,我们需要从 f[1][
] 转移,且 start 是当前区间的开始,即上一段的结束位置+1。
我们需要 map 存储 f[i-1][start-1],其中 start 是当前区间的开始。
初始化 map 为空,然后遍历 j 从 0 到 n-1,在计算 f[i][j] 前,我们需要将 start = j 的情况加入 map?不对,应该是将 f[i-1][j-1] 加入 map[pre[j]],因为 start=j 对应 pre[start]=pre[j]。
但 start 从0开始,所以我们需要先将 f[i-1][-1] 加入 map[pre[0]=0]。
所以,对于每个 i,我们先设置 map[pre[0]] = f[i-1][-1]。
然后遍历 j 从 0 到 n-1:
计算 needed = pre[j+1] - target[i-1]
cand1 = map[needed] + 1 if needed in map else INF
cand2 = (pre[j] == needed) ? f[i-1][j-1] : INF
f[i][j] = min(cand1, cand2)
更新 map[pre[j+1]] = min(map.get(pre[j+1], INF), f[i-1][j])
注意,f[i-1][j] 可能 INF。
对于 i=2,f[1][-1] 未定义,我们不需要,因为上一段必须结束于某个位置。
所以,对于 i=2,我们只从 f[1][*] 非 INF 的位置转移。
初始化 map 时,我们加入所有 start 对应的 f[i-1][start-1],但 start 从0开始,我们需要加入 f[i-1][-1] 如果存在。
实际上,我们可以让 f[i][j] 的 j 从 -1 到 n-1,其中 j=-1 表示未取任何元素。
但为了简化,我们只考虑 j>=0,且当 i=1 时,start 可以为0,对应 f[0][-1]=0。
所以,对于每个 i,我们初始化 map[pre[0]] = f[i-1][-1](如果 i=1,f[0][-1]=0;如果 i>1,f[i-1][-1] 不存在,设为 INF)。
然后遍历 j 从 0 到 n-1。
对于 i=2:
f[1][-1] 不存在,所以 map[0]=INF。
然后遍历 j:
j=0: needed=pre[1]-7=1-7=-6,cand1=INF。 pre[0]=0,needed=-6,不等,cand2=INF。 f[2][0]=INF。
更新 map[pre[1]=1] = min(INF, f[1][0]=INF) = INF。
j=1: needed=pre[2]-7=3-7=-4,cand1=INF。 pre[1]=1,不等,cand2=INF。 f[2][1]=INF。
更新 map[pre[2]=3] = min(INF, f[1][1]=1) = 1。
j=2: needed=pre[3]-7=6-7=-1,cand1=INF。 pre[2]=3,不等,cand2=INF。 f[2][2]=INF。
更新 map[pre[3]=6] = min(INF, f[1][2]=INF) = INF。
j=3: needed=pre[4]-7=10-7=3,map[3]=1,cand1=1+1=2。 pre[3]=6,不等于3,cand2=INF。 f[2][3]=2。
更新 map[pre[4]=10] = min(INF, f[1][3]=INF) = INF。
所以 f[2][3]=2,即匹配两个 target 元素,结束于 j=3,操作数=2。
最终答案 = f[2][3] = 2,正确。


第六步:算法总结

  1. 如果 sum(arr) != sum(target),返回 -1。
  2. 计算 arr 的前缀和 pre,长度 n+1。
  3. 初始化 dp 数组 f,维度 (m+1) x n,所有值为 INF。f[0][-1] 不存在,我们特殊处理,用 map 初始化。
  4. 对于 i 从 1 到 m:
    a. 创建哈希表 map,存储 key=前缀和值,value=最小 f[i-1][start-1]。
    b. 初始化 map[pre[0]] = 0 if i==1 else INF,因为 i=1 时,start=0 对应 f[0][-1]=0。
    c. 对于 j 从 0 到 n-1:
    needed = pre[j+1] - target[i-1]
    cand1 = map[needed] + 1 if needed in map else INF
    cand2 = f[i-1][j-1] if j>=0 and pre[j] == needed else INF
    f[i][j] = min(cand1, cand2)
    if f[i-1][j] < INF:
    map[pre[j+1]] = min(map.get(pre[j+1], INF), f[i-1][j])
  5. 答案 = f[m][n-1],如果为 INF 则返回 -1。

注意:f[i-1][j-1] 当 j=0 时不存在,视为 INF。
复杂度 O(mn),空间 O(mn) 可滚动优化为 O(n)。


第七步:示例验证
arr = [1,1,1,1], target=[2,2]
pre=[0,1,2,3,4]
m=2, n=4。
i=1: target[0]=2
map={0:0}
j=0: needed=1-2=-1, cand1=INF, pre[0]=0!=-1, cand2=INF -> f[1][0]=INF, map[1]=min(INF,INF)=INF
j=1: needed=2-2=0, map[0]=0 -> cand1=0+1=1, pre[1]=1!=0 -> cand2=INF -> f[1][1]=1, map[2]=min(INF,f[0][1]=INF)=INF
j=2: needed=3-2=1, map[1]=INF -> cand1=INF, pre[2]=2!=1 -> cand2=INF -> f[1][2]=INF, map[3]=INF
j=3: needed=4-2=2, map[2]=INF -> cand1=INF, pre[3]=3!=2 -> cand2=INF -> f[1][3]=INF, map[4]=INF
i=2: target[1]=2
map={0:INF}
j=0: needed=1-2=-1 -> INF, f[1][-1] 不存在,cand2=INF -> f[2][0]=INF, map[1]=min(INF,f[1][0]=INF)=INF
j=1: needed=2-2=0, map[0]=INF -> cand1=INF, pre[1]=1!=0 -> cand2=INF -> f[2][1]=INF, map[2]=min(INF,f[1][1]=1)=1
j=2: needed=3-2=1, map[1]=INF -> cand1=INF, pre[2]=2!=1 -> cand2=INF -> f[2][2]=INF, map[3]=min(INF,f[1][2]=INF)=INF
j=3: needed=4-2=2, map[2]=1 -> cand1=1+1=2, pre[3]=3!=2 -> cand2=INF -> f[2][3]=2
答案=2,正确。

如果 arr=[3], target=[3],则 n=1,m=1。
i=1: target[0]=3
map={0:0}
j=0: needed=pre[1]-3=3-3=0, map[0]=0 -> cand1=0+1=1, pre[0]=0==0 -> cand2=f[0][-1]=0 -> f[1][0]=min(1,0)=0
更新 map[pre[1]=3]=min(INF,f[0][0]=INF)=INF
答案 f[1][0]=0,操作次数0,因为区间长度=1,不需要合并。


这样就得到了一个 O(m*n) 的动态规划解法,可以处理元素有正、负、零的情况,并最小化操作次数。

合并区间形成目标数组的最小操作次数 题目描述 给定一个初始数组 arr ,其中每个元素都是整数,以及一个目标数组 target ,长度与 arr 相同。你可以对 arr 执行任意次数的操作:每次操作可以选择 arr 中一个 连续的子数组 ,并将该子数组中的所有元素合并(即用它们的和替换这个子数组)。合并后,子数组的长度变为1,其值为原子数组元素的和。操作会改变数组的长度,最终需要将 arr 转化为与 target 完全相同的数组(长度和每个位置的元素都相等)。问:从初始 arr 变换到目标 target 的 最小操作次数 是多少?如果无法变换,则返回 -1。 示例 初始数组: arr = [ 1, 2, 3, 4 ] 目标数组: target = [ 3, 7 ] 一种方案: 合并子数组 [ 1, 2] 得到 [ 3, 3, 4 ] 合并子数组 [ 3, 4] 得到 [ 3, 7 ] 操作次数 = 2。 输出: 2 解题思路 这道题本质是:我们有一系列数字,每次可以将一个连续区间合并为一个数(区间和),最终要得到目标数组。这类似于“通过合并相邻元素来匹配目标序列”。 第一步:问题转化与可行性判断 首先,无论怎么合并,数组元素的总和不会变。因此,如果 sum(arr) != sum(target) ,直接返回 -1。 其次,目标数组 target 可以看作是将 arr 划分成若干段,每段合并后的和恰好等于 target 中对应位置的值。例如,arr = [ 1,2,3,4],target = [ 3,7],就是把 arr 分成两段:[ 1,2] 和 [ 3,4 ],每段和分别为 3 和 7。 所以,问题转化为:能否将 arr 划分成若干连续段,使得第 i 段的元素和等于 target[ i ],并且我们要求的是合并操作的最小次数。 注意:每次操作合并一个连续子数组,该子数组内可以包含多个数,合并后子数组变成1个数。 如果某一段长度是 L,那么要将这一段合并成1个数,需要 L-1 次操作(每次合并减少一个元素,直到只剩一个数)。 但是,如果我们可以在一次操作中直接合并整个段吗?题目允许合并任意连续子数组,所以一次操作可以把一个段(长度 L)直接合并为1个数。 那么,对于一段长度为 L 的子数组,我们只需要 1 次操作就可以把它合并成1个数。不过,如果我们分多次合并(比如先合并其中一部分,再合并其他部分),可能操作次数更多,显然不是最优。因此,对于每一段,我们总是用一次操作把它合并成一个数。 那么,总的操作次数 = 段数(因为每个段一次操作)。但这里有一个陷阱:初始时 arr 长度是 n,目标是长度 m,我们要合并成 m 段,那么需要减少 (n - m) 个元素。每次操作可以减少 (选择的子数组长度 - 1) 个元素。 如果我们一次合并的区间长度是 L,那么减少的元素数是 L-1。 设我们最终划分成了 k 段(k = len(target)),那么总减少元素数 = n - k。 假设我们对第 i 段(长度 L_ i)进行一次合并操作,这次操作减少的元素数为 L_ i - 1。 总操作次数 = 合并次数,并且总减少元素数 = Σ (L_ i - 1) = (Σ L_ i) - k = n - k。 所以,无论我们如何安排合并,只要我们把数组划分成 k 段,并且每段合并一次,总操作次数就是 k 次吗?不对,因为 k 是目标长度,也就是最终段数。我们要做的合并次数 = 初始段数 - 最终段数 + ??? 等等,我们仔细推导: 初始有 n 个数,最终有 k 个数。每次合并操作将连续若干个数字合并成1个数字,所以一次操作会减少 (len-1) 个数字。设操作次数为 x,每次操作减少的数字数为 r_ i,则总减少数字数 = Σ r_ i = n - k。 因为每次操作减少数字数至少为 1(当合并长度为2时,减少1个数字),最多为 n-1(一次性合并整个数组)。 如果我们想最小化操作次数 x,那么我们希望每次操作减少尽可能多的数字,即每次合并尽可能长的连续段。 但合并的段必须对应目标数组的一段,也就是说,每次合并的区间必须是一个“完整的目标段”,否则合并后得到的数与目标不对应。 所以,我们必须先把 arr 划分成与 target 对应的段,然后每个段单独合并一次。 那么,每个段只需要一次操作(合并整个段),总操作次数 = 段数 k。 但是,有没有可能通过跨段合并来减少操作次数?不可以,因为跨段合并会使得段的和不符合 target 的某个数,除非跨的多个段的和正好等于 target 的某个数,但这样 target 的定义就变了,因为 target 已经固定了每个位置的数。 实际上,我们必须保证最终数组的每个位置恰好是 target[ i],所以最终数组的第 i 个数一定是 arr 中某连续几个数的和,且这个和等于 target[ i ]。因此,arr 必须能划分成若干连续子数组,每个子数组和等于 target 的对应元素。 如果这种划分存在,那么每个子数组(长度 L_ i)我们只需一次操作合并它。所以总操作次数 = 目标数组长度 m。 然而,这是正确的吗?让我们检查示例:arr=[ 1,2,3,4], target=[ 3,7 ],m=2,计算得到 2 次操作,与示例一致。 再试一个例子:arr=[ 1,1,1,1], target=[ 2,2],划分成[ 1,1]和[ 1,1 ],各需一次合并,总操作=2。 但如果我们先合并前两个1得到[ 2,1,1],再合并后两个1得到[ 2,2 ],操作次数=2,与直接每段一次操作相同。 再考虑 arr=[ 1,2,3], target=[ 6],划分成整个数组[ 1,2,3 ]和为6,一次操作即可。m=1,操作=1。 所以看起来总操作次数 = len(target)。 但等等,题目要求的是最小操作次数,如果某一段长度已经是1,我们还需要合并吗?不需要,因为已经是一个数了。 所以,对于长度已经是1的段(即 arr 中某个单独的数正好等于 target 的某个数),我们不需要操作。 那么,设划分后,第 i 段的长度是 L_ i,如果 L_ i = 1,则不需要操作;如果 L_ i > 1,则需要 1 次操作。 总操作次数 = 满足 L_ i > 1 的段的数量。 这样,我们要最小化操作次数,就要尽可能让划分出的段中,长度=1的段尽可能多。 但划分是固定的吗?不,因为 arr 划分成若干段,每段和等于 target[ i ],但可能有多种划分方式(如果 arr 中有0,可能影响分段长度,但元素都是正整数?题目没说,假设都是正整数)。 如果都是正整数,那么和相等的连续子数组划分是唯一的(因为前缀和严格递增)。 所以,如果 arr 和 target 的元素都是正整数,那么划分方式唯一。我们只需检查能否划分,然后数一下长度>1的段数即可。 但题目没有说元素为正整数,可能是任意整数,包括0。那么,如果包含0,则划分可能不唯一,因为0不影响和,但可以合并到左边或右边段,从而改变段长度。 这时,我们可以通过选择划分,使得更多段长度为1,从而减少操作次数。 这就是问题的难点:我们需要找到一种划分方式,使得“长度>1的段”的数量最小化。 第二步:动态规划状态定义 设 n = len(arr), m = len(target)。 我们定义 dp[ i ] 表示考虑 arr 的前 i 个元素,匹配了 target 的前 j 个元素时的最小操作次数?不对,应该是同时遍历 arr 和 target。 我们定义 dp[ i][ j] 表示:使用 arr 的前 i 个元素(即 arr[ 0..i-1]),匹配 target 的前 j 个元素(即 target[ 0..j-1 ])的最小操作次数。但这样状态转移复杂,因为 i 和 j 不是同步增长的。 另一种思路:由于我们必须将 arr 划分成 m 段,每段和等于 target[ j ],我们可以用指针在 arr 上移动,同时用 j 表示当前 target 段索引。 定义 dp[ j ] 表示匹配到 target 的第 j 段时,所用的 arr 的最小前缀长度,以及对应的最小操作次数?这样不好。 更好的做法:区间DP,但这里 arr 是固定的,我们只需要在 arr 上找若干分割点,使得每段和等于 target 对应值。 设目标有 m 段,我们需要在 arr 中找到 m-1 个分割点,0 = p0 < p1 < p2 < ... < p_ {m-1} < p_ m = n,使得 sum(arr[ p_ {k-1} .. p_ k - 1]) = target[ k-1 ] 对每个 k 成立。 如果有多组分割点,我们要选择一种,使得“长度>1的段”的数量最少。 因为 target 的和与 arr 总和相等,所以分割点必须满足前缀和相等条件。 定义 arr 的前缀和数组 pre,pre[ 0]=0, pre[ i]=sum(arr[ 0..i-1 ])。 定义 target 的前缀和 tpre,tpre[ 0]=0, tpre[ j]=sum(target[ 0..j-1 ])。 那么,第 j 段对应的 arr 区间 [ l, r) 必须满足 pre[ r] - pre[ l] = target[ j ]。 且 tpre[ j] = pre[ r ]。 所以,分割点 r 必须满足 pre[ r] 在 tpre 中出现,并且 pre[ r] = tpre[ j ] 对于某个 j。 那么,我们可以将 tpre 的值映射到下标 j。 我们从左到右扫描 arr 前缀和,当 pre[ i] = tpre[ j ] 时,说明我们可以完成前 j 段,且最后一段的右端点是 i。 那么,设 f[ j] 表示匹配完前 j 段(即 target[ 0..j-1]),且最后一段以 i 结尾(pre[ i]=tpre[ j ])时的最小操作次数。 但 i 是什么?我们其实不关心具体的 i,只关心最后一个段的长度。 可以这样:dp[ j] 表示匹配完前 j 段的最小操作次数,且我们知道此时 arr 用到前缀 pre 的某个位置 i,满足 pre[ i]=tpre[ j ]。 状态转移:dp[ j] = min_ {0 <= k < j} (dp[ k] + cost(k, j)),其中 cost(k, j) 表示将 arr 中对应 target 的第 k 段到第 j-1 段(即 target[ k..j-1 ])划分出来,这一大段(对应 arr 的一个区间)如果长度大于1,则需要1次操作,否则0次。但注意,我们并不需要一次操作合并整个大段,因为我们可以对其中每个小段分别操作。 实际上,我们是将 arr 划分成 m 段,每段对应一个 target 元素。如果我们把相邻的若干个小段看作一个整体,但这样合并后得到的和不是 target 的单个元素,所以不行。我们必须保证每个 target 元素对应 arr 的一个连续子数组,不能合并多个 target 元素对应的段。 所以,每个 target 元素单独对应一段 arr,不可合并多个 target 元素。 那么,cost(k, j) 实际上只针对一段,即 j 是单个段。 所以,dp 的转移应该是:dp[ j] = dp[ j-1] + (L_ j > 1 ? 1 : 0),其中 L_ j 是 arr 中对应 target[ j-1 ] 的段的长度。 但 L_ j 可能不唯一,因为 arr 中可能有多个区间和等于 target[ j-1]。我们需要选择长度最长的还是最短的?我们要最小化操作次数,所以如果该段长度=1,则操作+0;如果长度>1,则操作+1。我们希望长度=1的段尽可能多,所以对于给定的 target[ j-1 ],我们希望找到 arr 中和等于它的区间,且长度=1的区间优先。 但区间是连续的,且必须按顺序匹配 target。 所以,我们从左到右匹配 target 的每个元素,在 arr 中寻找一段区间,其和等于 target[ j ],且紧接着上一段的结束位置。 如果存在多个结束位置(因为 arr 中有0,可以延长区间而不改变和),我们可以选择结束位置最靠左的(即长度最短的),这样更可能让后面的段也有机会长度为1吗?不一定。 因此,我们需要搜索所有可能的分段方式,找到最少数量的长度>1的段。 这是一个典型的序列分割DP。 定义 dp[ i ] 表示匹配完 target 的前 i 个元素时,所用的 arr 的最小操作次数,且此时 arr 的结束位置是某个 idx(我们记下这个 idx)。 转移:dp[ i] = min_ {最后一段的开始位置 s} (dp[ i-1] + (最后一段长度>1 ? 1 : 0)),其中最后一段是 arr[ s..e],且 sum = target[ i-1 ],且 s 等于上一段的结束位置。 所以,我们需要知道从 arr 的某个位置 start 开始,和为 target[ i ] 的区间结束位置。 如果 arr 元素非负,则对于固定的 start,和为 target[ i ] 的结束位置是唯一的(如果存在),因为前缀和单调不减。 如果 arr 元素有正有负,则可能有多个结束位置。 我们可以预处理出每个 start 和每个 target 值对应的所有可能结束位置。 但 n 和 m 可达 1000 左右,所以 O(n* m) 可行。 第三步:状态转移方程 设 dp[ i] 表示匹配完 target 的前 i 个元素时的最小操作次数,且此时 arr 的结束位置是 pos[ i ](即最后一段的右边界+1)。 初始化 dp[ 0]=0, pos[ 0 ]=0。 对于每个 i 从 1 到 m: 我们需要找到从 pos[ i-1] 开始的一段连续子数组,其和等于 target[ i-1 ]。 设从 start = pos[ i-1] 开始,我们尝试结束位置 end,使得 sum(arr[ start..end]) = target[ i-1 ]。 如果找到,则 pos[ i] = end+1,且 dp[ i] = dp[ i-1 ] + (end-start+1 > 1 ? 1 : 0)。 但可能有多个 end 满足条件(如果 arr 有负数或0),我们要选择使总操作数最小的那个。 所以,dp[ i] = min_ {end} (dp[ i-1] + (len>1?1:0)),其中 len = end-start+1,且 sum(arr[ start..end])=target[ i-1 ]。 同时记录 pos[ i ] = end+1。 但 pos[ i ] 会随着 end 变化,不同的 end 会影响后面的匹配。 所以,我们实际上需要二维状态:dp[ i][ e ] 表示匹配前 i 个 target 元素,且最后一个 target 元素对应 arr 的结束位置是 e 时的最小操作次数。 转移:dp[ i][ e] = min_ {s} (dp[ i-1][ s] + (e-s > 0 ? 1 : 0)),其中 s 是上一个结束位置+1,即 s = prev_ end+1,且 sum(arr[ s..e]) = target[ i-1 ]。 但 prev_ end 是上一个匹配的结束位置,即对于 dp[ i-1][ s' ] 中的 s' 其实是上一个结束位置+1?我们调整一下。 设 f[ i][ j ] 表示匹配前 i 个 target 元素,且第 i 个元素对应 arr 的区间结束于位置 j(即 arr 的索引 j,从0开始)的最小操作次数。 则第 i 个元素对应的区间是 [ k+1, j ],其中 k 是上一个结束位置。 条件:sum(arr[ k+1 .. j]) = target[ i-1 ]。 转移:f[ i][ j] = min_ {k < j} (f[ i-1][ k] + (j-k > 1 ? 1 : 0)),其中 sum(arr[ k+1..j]) = target[ i-1 ]。 初始 f[ 0][ -1 ] = 0,我们设 k=-1 表示开始前。 最终答案 = min_ {j} f[ m][ j ],其中 j = n-1 必须成立,因为要用完 arr 所有元素。 复杂度 O(m * n^2) 太高,需要优化。 注意,sum(arr[ k+1..j]) 可以用前缀和快速计算,即 pre[ j+1]-pre[ k+1 ]。 条件 pre[ j+1]-pre[ k+1] = target[ i-1] => pre[ k+1] = pre[ j+1] - target[ i-1 ]。 所以,对于固定的 j 和 i,我们需要找到所有 k 满足 k < j 且 pre[ k+1] = pre[ j+1] - target[ i-1 ]。 我们可以用哈希表记录每个前缀和值对应的所有位置下标(指的是 pre 数组的下标,即前缀个数)。 但 k+1 的范围是 1..n,我们记 pos_ sum[ val] 为所有下标 t 使得 pre[ t ] = val。 那么,对于每个 j,我们计算 needed = pre[ j+1] - target[ i-1],然后在 pos_ sum[ needed] 中找到所有 t 满足 t-1 < j,即 t <= j,且 t >= i(因为至少要 i 个元素?其实不需要,只要 t>0)。 但我们需要 t-1 是上一个结束位置,即第 i-1 段结束于 t-1。 所以,f[ i][ j] = min_ {t in pos_ sum[ needed], t-1 < j} (f[ i-1][ t-1 ] + (j - (t-1) > 1 ? 1 : 0))。 其中 len = j - (t-1) = j - t + 1。 所以,f[ i][ j] = min_ {t} (f[ i-1][ t-1 ] + (j-t+1 > 1 ? 1 : 0))。 即 f[ i][ j] = min_ {t} (f[ i-1][ t-1 ] + (j-t >= 1 ? 1 : 0))。 如果 j==t-1,则区间长度为0,不可能,因为区间和为正?除非 target[ i-1]=0 且区间长度0,但区间长度0时和为0,所以如果 target[ i-1 ]=0,我们可以取空区间,但题目要求合并操作,区间长度至少1?实际上区间必须至少1个元素,因为合并需要至少1个元素。但 target 元素为0时,arr 中对应段和=0,可以是一段0,长度任意。 暂时假设 target 元素非零,则区间长度至少1。 那么,j-t+1 >=1 => j>=t。 而 t <= j 且 t-1 < j 恒成立。 所以,我们只需要 t <= j 且 t 在 pos_ sum[ needed ] 中。 这样,我们可以用前缀最小值优化: 对于每个 i,我们遍历 j 从 0 到 n-1,对于每个 j,需要 needed = pre[ j+1] - target[ i-1 ]。 然后遍历所有 t 满足 pre[ t] = needed 且 t <= j。 在遍历过程中,我们维护两个最小值: min0 = min_ {t} f[ i-1][ t-1 ] (当区间长度=1,即 j-t+1=1 => j=t) min1 = min_ {t} f[ i-1][ t-1 ] + 1 (当区间长度>1,即 j>=t+1) 但区间长度=1 对应 j = t,区间长度>1 对应 j >= t+1。 所以,对于固定的 j,我们考虑所有 t <= j,如果 j==t,则候选值 = f[ i-1][ t-1];否则候选值 = f[ i-1][ t-1 ]+1。 我们可以分别维护两个数组: best0[ j] = 对于所有 t 满足 pre[ t]=needed 且 t<=j,当 j==t 时的 f[ i-1][ t-1 ] 的最小值。 best1[ j] = 对于所有 t 满足 pre[ t]=needed 且 t<=j,当 j>t 时的 f[ i-1][ t-1 ] 的最小值。 但 t 和 j 有关,不好直接维护。 我们可以换一种方式:先遍历 t,再更新对应的 j。 对于每个 t,我们需要更新所有 j >= t 且 pre[ j+1] = pre[ t] + target[ i-1 ]。 设 val = pre[ t] + target[ i-1 ]。 我们找到所有 j 满足 pre[ j+1 ] = val 且 j >= t。 对于这些 j,如果 j==t,则区间长度=1,用 f[ i-1][ t-1] 更新 f[ i][ j];否则用 f[ i-1][ t-1 ]+1 更新。 这样,我们可以用两个数组 min_ val0 和 min_ val1 来记录,但 j 是离散的。 实际上,我们可以对每个 val,记录所有 j 的位置,然后对于每个 t,我们找到所有 j 的位置,进行更新。 但这样复杂度可能 O(n^2),但 n <=1000 可以接受。 更简单的实现:直接用 O(m n^2) 的 DP,n,m<=100 可以,但题目可能 n,m <=1000,需要优化。 假设 n,m<=1000,O(m n^2)=1e9 太大。 我们需要优化到 O(m n) 或 O(n^2)。 注意到,对于每个 i,我们只关心 f[ i-1][ ],所以可以滚动数组。 主要瓶颈是对于每个 (i,j),要枚举 t。 我们可以用哈希表记录每个前缀和值对应的 t 列表,然后对于每个 j,我们遍历所有 t 满足 pre[ t] = needed 且 t <=j。 如果每个前缀和值对应的 t 很多,最坏 O(n),则总 O(m n^2)。 但我们可以优化:对于每个 i 和每个 needed 值,我们按 t 递增顺序遍历,维护 f[ i-1][ t-1 ] 的最小值。 具体地,对于每个 i,我们遍历 j 从 0 到 n-1,同时维护一个哈希表 map,其中 key=needed,value=到目前为止对于该 needed 的 min(f[ i-1][ t-1]),其中 t 满足 pre[ t]=needed 且 t <=当前考虑的某个值。 但我们需要区分 j==t 和 j>t 的情况。 我们可以维护两个值:min_ same 和 min_ diff,其中 min_ same 对应 j==t 的情况,min_ diff 对应 j>t 的情况。 实际上,我们可以这样做: 对于每个 needed 值,我们维护一个最小值 min_ val,即所有 t 对应的 f[ i-1][ t-1 ] 的最小值。 然后,对于每个 j,我们计算 needed,然后 f[ i][ j] = min_ val + 1,除非存在 t 使得 j==t,此时可以用 f[ i-1][ t-1 ] 更新(不加1)。 所以,我们需要知道是否存在 t 满足 pre[ t ]=needed 且 t=j。 我们可以提前建立前缀和到位置列表的映射。 然后,对于每个 j,我们查看 needed 是否等于 pre[ j]?注意 needed = pre[ j+1] - target[ i-1 ]。 我们令 t 满足 pre[ t ]=needed,且 t 是整数 1..n。 如果存在 t 使得 t = j+1?因为 t 是 pre 数组下标,对应 arr 的前 t 个元素的和。t 和 j 的关系:区间是 [ t, j] 对应 arr 的下标从 t 到 j(0-based),和为 pre[ j+1]-pre[ t ]。 我们设 start = t,则区间为 [ start, j],和为 pre[ j+1]-pre[ start] = target[ i-1 ]。 所以 needed = pre[ start] = pre[ j+1] - target[ i-1 ]。 所以,当 start = j+1 时,区间长度为0,不可能,除非 target[ i-1 ]=0。 区间长度=1 对应 start = j,即区间 [ j, j ]。 所以,当 start = j 时,区间长度=1。此时 needed = pre[ j] = pre[ j+1] - target[ i-1 ]。 所以,我们需要检查是否存在 start 使得 pre[ start ]=needed 且 start = j。 即 needed 是否等于 pre[ j ]。 因此,对于每个 j,我们计算 needed = pre[ j+1] - target[ i-1 ]。 如果 pre[ j] == needed,则存在一个区间长度=1 的情况,此时 f[ i][ j] 可以用 f[ i-1][ j-1 ] 更新(不加1)。 同时,对于其他 start < j,我们可以用 min_ {start < j} f[ i-1][ start-1 ] + 1 更新。 所以,我们维护一个数组 minF[ start-1] 的最小值,其中 start 满足 pre[ start ] = needed。 我们可以用哈希表 map[ needed] = 目前为止所有 start 对应的 f[ i-1][ start-1 ] 的最小值。 那么,对于每个 j,我们计算 needed,然后: cand1 = map[ needed] + 1 // 对应 start < j cand2 = 如果 pre[ j] == needed,则 f[ i-1][ j-1 ] f[ i][ j ] = min(cand1, cand2) 但注意,start 可以等于 j,此时就是 cand2 的情况,我们单独处理。 而 map[ needed] 应该包含所有 start <= j 的情况吗?不,我们只需要 start < j 的情况,因为 start=j 单独处理。 所以,我们需要在遍历 j 时,动态更新 map:当遍历到 j 时,我们先计算 f[ i][ j],然后再将 start = j+1 的情况加入 map?不对,因为 start 是下一个区间的开始,而我们现在计算的是 f[ i][ j ],其中 j 是当前区间的结束。 实际上,对于当前 i,我们需要的是上一个状态 f[ i-1][ start-1 ],其中 start 是当前区间的开始。 我们可以在遍历 j 之前,先把所有 start 对应的 f[ i-1][ start-1] 按 pre[ start ] 分组,得到每个 needed 对应的最小值。 但 needed 依赖于 j,因为 needed = pre[ j+1] - target[ i-1 ],所以对于不同的 j,needed 不同。 所以,我们需要对于每个 needed 值,维护 f[ i-1][ start-1] 的最小值,其中 pre[ start ] = needed。 然后对于每个 j,我们计算 needed,查询 map[ needed] 得到 min_ {start} f[ i-1][ start-1],但这里的 start 可以任意,我们需要 start <= j 吗?需要,因为区间 [ start, j] 要求 start <= j。 所以,我们需要的是 start <= j 的那些 start 的最小值。 那么,我们可以按 j 递增顺序遍历,同时维护每个 needed 对应的最小值,随着 j 增加,我们将 start = j 的情况加入 map(注意,start 是下一个区间的开始,所以当 j 增加时,我们可以将 start = j 对应的 f[ i-1][ j-1] 加入 map[ pre[ j] ])。 具体步骤: 初始化 map 为空。 对于 j 从 0 到 n-1: 1. 计算 needed = pre[ j+1] - target[ i-1 ]。 2. 如果 map 中存在 needed,则 cand1 = map[ needed ] + 1,否则 cand1 = INF。 3. 如果 pre[ j] == needed,则 cand2 = f[ i-1][ j-1 ](当 j>0),否则 cand2 = INF。 4. f[ i][ j ] = min(cand1, cand2)。 5. 更新 map[ pre[ j+1]] = min(map[ pre[ j+1]], f[ i-1][ j])。注意,这里 pre[ j+1] 是下一个区间的 start 对应的前缀和,因为 start 对应 pre[ start],而 start = j+1 时,pre[ start] = pre[ j+1 ]。 注意,f[ i-1][ j ] 是前 i-1 段匹配完且结束于 j 的最小操作数。 这样,我们在计算 f[ i][ j] 时,map[ needed] 中存储的是所有 start <= j 的 f[ i-1][ start-1 ] 的最小值吗? 因为我们是在计算完 f[ i][ j] 后才将 f[ i-1][ j] 加入 map,而 map 的 key 是 pre[ start] = pre[ j+1],这个 start 是 j+1,大于 j,所以对于后面的 j' > j,这个 start 是 <= j' 的。 因此,对于每个 needed,map[ needed] 存储的是所有 start <= 当前 j 的 f[ i-1][ start-1 ] 的最小值。 但注意,start 是当前区间的开始,start 的范围是 1..n。 我们在遍历 j 时,逐步将 start = j+1 的情况加入 map,所以当计算 f[ i][ j] 时,map 中包含的是 start <= j 的情况吗?不,因为 start = j+1 是大于 j 的,我们加入的是下一个 start,所以对于当前的 j,map 中包含的是 start <= j 的情况吗? 我们加入的是 f[ i-1][ j ],对应的 start = j+1,这个 start 是大于 j 的。 所以,实际上,对于当前的 j,map 中包含的是 start <= j 的情况吗?我们需要仔细分析。 假设 j=0,计算 f[ i][ 0] 时,map 是空的,然后我们将 f[ i-1][ 0 ] 加入 map,对应的 start=1。 当 j=1 时,map 中包含 start=1 的情况,而 start=1 <=1,所以对于 j=1,map 中包含 start=1 的情况,即 start <= j 的情况。 所以,确实,当我们计算 f[ i][ j] 时,map 中包含的是所有 start <= j 的 f[ i-1][ start-1 ] 的最小值。 因为我们在上一步 j-1 时将 start = j 的情况加入了 map。 所以,这个算法正确。 复杂度:对于每个 i,遍历 j 从 0 到 n-1,每次 O(1) 查询和更新 map。 总复杂度 O(m n)。 第四步:边界条件与初始化 初始化 f[ 0][ -1] = 0,但数组下标没有 -1,我们可以用 f[ 0][ j] 表示匹配0个 target 元素,且结束于 j 的最小操作数,只有 j=-1 是合法的,但我们可以用 f[ 0][ j] 表示匹配0个元素且结束于 j,但此时必须 j=-1,所以我们用 f[ 0][ j] 表示匹配0个元素且结束于 j 是不可能的,设为 INF,除了 f[ 0][ -1 ]=0。 实现时,我们让下标从0开始,用 f[ i][ j ] 表示匹配前 i 个元素,且结束于 j(0-based)的最小操作数。 我们增加一个虚拟位置 -1,用 f[ i][ j ] 的 j 从 0 到 n-1。 初始化 f[ 0][ j] 全部 INF,但我们可以用 f[ 0][ -1 ]=0,然后从 i=1 开始,j 从 0 到 n-1 计算。 在计算 f[ i][ j] 时,如果 j=0,则 cand2 需要 f[ i-1][ -1],我们可以用 prev0 表示 f[ i-1][ -1 ],即上一段结束于 -1 的最小操作数。 我们可以在循环外维护一个变量 prev0 表示 f[ i-1][ -1 ]。 对于 i=1,prev0 = f[ 0][ -1 ] = 0。 然后,对于每个 i,我们遍历 j 从 0 到 n-1,更新 f[ i][ j ],同时维护 map。 最终,答案 = f[ m][ n-1 ],如果为 INF 则返回 -1。 第五步:示例推演 arr = [ 1,2,3,4], target = [ 3,7 ] pre = [ 0,1,3,6,10 ] target 前缀和 tpre=[ 0,3,10 ] m=2, n=4。 初始化 f[ 0][ -1 ]=0。 i=1, target[ 0 ]=3。 初始化 map 为空。 j=0: needed = pre[ 1]-3=1-3=-2,map 中无,cand1=INF。 pre[ 0]=0,needed=-2,不等,cand2=INF。 f[ 1][ 0 ]=INF。 更新 map[ pre[ 1]=1] = min(INF, f[ 0][ 0]),但 f[ 0][ 0] 不存在,我们只考虑 f[ 0][ -1],但 f[ 0][ j] 对于 j>=0 都是 INF,所以 map[ 1 ]=INF。 j=1: needed=pre[ 2]-3=3-3=0,map[ 0] 不存在,cand1=INF。 pre[ 1]=1,needed=0,不等,cand2=INF。 f[ 1][ 1 ]=INF。 更新 map[ pre[ 2]=3] = min(INF, f[ 0][ 1 ]) = INF。 j=2: needed=pre[ 3]-3=6-3=3,map[ 3] 不存在,cand1=INF。 pre[ 2]=3,needed=3,相等,cand2 = f[ 0][ 1] = INF。 f[ 1][ 2 ]=INF。 更新 map[ pre[ 3]=6 ] = INF。 j=3: needed=pre[ 4]-3=10-3=7,map[ 7] 不存在,cand1=INF。 pre[ 3]=6,不等于7,cand2=INF。 f[ 1][ 3 ]=INF。 更新 map[ pre[ 4]=10 ]=INF。 发现 f[ 1][ ] 全部 INF,说明无法匹配第一个 target 元素?但我们知道应该是可以的。 错误在哪里? needed = pre[ j+1] - target[ i-1 ]。 当 i=1, j=1: needed=pre[ 2 ]-3=3-3=0。 但 pre[ 0]=0,所以 needed=0 对应 start=0,即区间 [ 0,1] 的和为 pre[ 2]-pre[ 0]=3-0=3,匹配 target[ 0 ]=3。 此时 start=0,我们需要 f[ i-1][ start-1] = f[ 0][ -1 ] = 0。 但我们的算法中,map[ needed] 存储的是 f[ i-1][ start-1],其中 start 是当前区间的开始,而 start 对应 pre[ start ]。 当 needed=0,我们需要 map[ 0] 包含 f[ 0][ -1],但 map[ 0] 在 j=1 时还未被加入,因为我们在 j=0 时加入的是 pre[ 1]=1,不是 pre[ 0 ]=0。 我们需要在开始遍历 j 之前,先将 start=0 的情况加入 map,即 f[ i-1][ -1] 对应 pre[ 0 ]=0。 所以,初始化 map[ pre[ 0]=0] = f[ i-1][ -1 ] = 0。 然后遍历 j 从 0 到 n-1。 重新计算: i=1, target[ 0 ]=3。 初始化 map = {0:0}。 j=0: needed=pre[ 1]-3=1-3=-2,不在 map,cand1=INF。 pre[ 0]=0,needed=-2,不等,cand2=INF。 f[ 1][ 0 ]=INF。 更新 map[ pre[ 1]=1] = min(INF, f[ 0][ 0 ]=INF) = INF。 j=1: needed=pre[ 2]-3=3-3=0,map[ 0]=0,cand1=0+1=1。 pre[ 1]=1,needed=0,不等,cand2=INF。 f[ 1][ 1 ]=1。 更新 map[ pre[ 2]=3] = min(INF, f[ 0][ 1 ]=INF) = INF。 j=2: needed=pre[ 3]-3=6-3=3,map[ 3]=INF,cand1=INF。 pre[ 2]=3,needed=3,相等,cand2=f[ 0][ 1]=INF。 f[ 1][ 2 ]=INF。 更新 map[ pre[ 3]=6 ] = INF。 j=3: needed=pre[ 4]-3=10-3=7,map[ 7] 无,cand1=INF。 pre[ 3]=6,不等,cand2=INF。 f[ 1][ 3 ]=INF。 所以 f[ 1][ 1 ]=1,表示匹配第一个 target 元素,结束于 j=1,操作数=1,区间长度=2>1,所以加1。 i=2, target[ 1 ]=7。 初始化 map = {0:0}?注意,每轮 i 需要重新初始化 map,包含 f[ i-1][ -1] 对应 pre[ 0 ]=0。 f[ i-1][ -1] 是 f[ 1][ -1],但 f[ 1][ -1 ] 未定义,我们只考虑结束于具体位置的情况。 实际上,对于 i=2,我们需要从 f[ 1][ ] 转移,且 start 是当前区间的开始,即上一段的结束位置+1。 我们需要 map 存储 f[ i-1][ start-1 ],其中 start 是当前区间的开始。 初始化 map 为空,然后遍历 j 从 0 到 n-1,在计算 f[ i][ j] 前,我们需要将 start = j 的情况加入 map?不对,应该是将 f[ i-1][ j-1] 加入 map[ pre[ j]],因为 start=j 对应 pre[ start]=pre[ j ]。 但 start 从0开始,所以我们需要先将 f[ i-1][ -1] 加入 map[ pre[ 0]=0 ]。 所以,对于每个 i,我们先设置 map[ pre[ 0]] = f[ i-1][ -1 ]。 然后遍历 j 从 0 到 n-1: 计算 needed = pre[ j+1] - target[ i-1 ] cand1 = map[ needed ] + 1 if needed in map else INF cand2 = (pre[ j] == needed) ? f[ i-1][ j-1 ] : INF f[ i][ j ] = min(cand1, cand2) 更新 map[ pre[ j+1]] = min(map.get(pre[ j+1], INF), f[ i-1][ j ]) 注意,f[ i-1][ j ] 可能 INF。 对于 i=2,f[ 1][ -1 ] 未定义,我们不需要,因为上一段必须结束于某个位置。 所以,对于 i=2,我们只从 f[ 1][* ] 非 INF 的位置转移。 初始化 map 时,我们加入所有 start 对应的 f[ i-1][ start-1],但 start 从0开始,我们需要加入 f[ i-1][ -1 ] 如果存在。 实际上,我们可以让 f[ i][ j ] 的 j 从 -1 到 n-1,其中 j=-1 表示未取任何元素。 但为了简化,我们只考虑 j>=0,且当 i=1 时,start 可以为0,对应 f[ 0][ -1 ]=0。 所以,对于每个 i,我们初始化 map[ pre[ 0]] = f[ i-1][ -1](如果 i=1,f[ 0][ -1]=0;如果 i>1,f[ i-1][ -1 ] 不存在,设为 INF)。 然后遍历 j 从 0 到 n-1。 对于 i=2: f[ 1][ -1] 不存在,所以 map[ 0 ]=INF。 然后遍历 j: j=0: needed=pre[ 1]-7=1-7=-6,cand1=INF。 pre[ 0]=0,needed=-6,不等,cand2=INF。 f[ 2][ 0 ]=INF。 更新 map[ pre[ 1]=1] = min(INF, f[ 1][ 0 ]=INF) = INF。 j=1: needed=pre[ 2]-7=3-7=-4,cand1=INF。 pre[ 1]=1,不等,cand2=INF。 f[ 2][ 1 ]=INF。 更新 map[ pre[ 2]=3] = min(INF, f[ 1][ 1 ]=1) = 1。 j=2: needed=pre[ 3]-7=6-7=-1,cand1=INF。 pre[ 2]=3,不等,cand2=INF。 f[ 2][ 2 ]=INF。 更新 map[ pre[ 3]=6] = min(INF, f[ 1][ 2 ]=INF) = INF。 j=3: needed=pre[ 4]-7=10-7=3,map[ 3]=1,cand1=1+1=2。 pre[ 3]=6,不等于3,cand2=INF。 f[ 2][ 3 ]=2。 更新 map[ pre[ 4]=10] = min(INF, f[ 1][ 3 ]=INF) = INF。 所以 f[ 2][ 3 ]=2,即匹配两个 target 元素,结束于 j=3,操作数=2。 最终答案 = f[ 2][ 3 ] = 2,正确。 第六步:算法总结 如果 sum(arr) != sum(target),返回 -1。 计算 arr 的前缀和 pre,长度 n+1。 初始化 dp 数组 f,维度 (m+1) x n,所有值为 INF。f[ 0][ -1 ] 不存在,我们特殊处理,用 map 初始化。 对于 i 从 1 到 m: a. 创建哈希表 map,存储 key=前缀和值,value=最小 f[ i-1][ start-1 ]。 b. 初始化 map[ pre[ 0]] = 0 if i==1 else INF,因为 i=1 时,start=0 对应 f[ 0][ -1 ]=0。 c. 对于 j 从 0 到 n-1: needed = pre[ j+1] - target[ i-1 ] cand1 = map[ needed ] + 1 if needed in map else INF cand2 = f[ i-1][ j-1] if j>=0 and pre[ j ] == needed else INF f[ i][ j ] = min(cand1, cand2) if f[ i-1][ j] < INF: map[ pre[ j+1]] = min(map.get(pre[ j+1], INF), f[ i-1][ j ]) 答案 = f[ m][ n-1 ],如果为 INF 则返回 -1。 注意:f[ i-1][ j-1 ] 当 j=0 时不存在,视为 INF。 复杂度 O(m n),空间 O(m n) 可滚动优化为 O(n)。 第七步:示例验证 arr = [ 1,1,1,1], target=[ 2,2 ] pre=[ 0,1,2,3,4 ] m=2, n=4。 i=1: target[ 0 ]=2 map={0:0} j=0: needed=1-2=-1, cand1=INF, pre[ 0]=0!=-1, cand2=INF -> f[ 1][ 0]=INF, map[ 1 ]=min(INF,INF)=INF j=1: needed=2-2=0, map[ 0]=0 -> cand1=0+1=1, pre[ 1]=1!=0 -> cand2=INF -> f[ 1][ 1]=1, map[ 2]=min(INF,f[ 0][ 1 ]=INF)=INF j=2: needed=3-2=1, map[ 1]=INF -> cand1=INF, pre[ 2]=2!=1 -> cand2=INF -> f[ 1][ 2]=INF, map[ 3 ]=INF j=3: needed=4-2=2, map[ 2]=INF -> cand1=INF, pre[ 3]=3!=2 -> cand2=INF -> f[ 1][ 3]=INF, map[ 4 ]=INF i=2: target[ 1 ]=2 map={0:INF} j=0: needed=1-2=-1 -> INF, f[ 1][ -1] 不存在,cand2=INF -> f[ 2][ 0]=INF, map[ 1]=min(INF,f[ 1][ 0 ]=INF)=INF j=1: needed=2-2=0, map[ 0]=INF -> cand1=INF, pre[ 1]=1!=0 -> cand2=INF -> f[ 2][ 1]=INF, map[ 2]=min(INF,f[ 1][ 1 ]=1)=1 j=2: needed=3-2=1, map[ 1]=INF -> cand1=INF, pre[ 2]=2!=1 -> cand2=INF -> f[ 2][ 2]=INF, map[ 3]=min(INF,f[ 1][ 2 ]=INF)=INF j=3: needed=4-2=2, map[ 2]=1 -> cand1=1+1=2, pre[ 3]=3!=2 -> cand2=INF -> f[ 2][ 3 ]=2 答案=2,正确。 如果 arr=[ 3], target=[ 3 ],则 n=1,m=1。 i=1: target[ 0 ]=3 map={0:0} j=0: needed=pre[ 1]-3=3-3=0, map[ 0]=0 -> cand1=0+1=1, pre[ 0]=0==0 -> cand2=f[ 0][ -1]=0 -> f[ 1][ 0 ]=min(1,0)=0 更新 map[ pre[ 1]=3]=min(INF,f[ 0][ 0 ]=INF)=INF 答案 f[ 1][ 0 ]=0,操作次数0,因为区间长度=1,不需要合并。 这样就得到了一个 O(m* n) 的动态规划解法,可以处理元素有正、负、零的情况,并最小化操作次数。