问题描述
小M拥有一个长度为n的数组 a,由于他非常喜欢数字 w,他希望将所有数组中的数都变为 w。
小M每次操作可以选择一个区间 [l, r],并将该区间内的所有数字都加 1(包括左右边界 l 和 r)。但为了让挑战更具趣味性,小M要求每次操作的l均不相同,r也均不相同。
小M现在想知道,有多少种不同的操作方案可以让数组中的所有数都变为 w。注意,如果所有操作的区间相同,则视为同一种操作方式,操作顺序不同并不会形成新的方案。
测试样例
样例1:
输入:n = 2 ,w = 2 ,array = [1, 1] 输出:2
样例2:
输入:n = 1 ,w = 1 ,array = [1] 输出:1
样例3:
输入:n = 3 ,w = 5 ,array = [5, 4, 5] 输出:1
方法思路
这个问题的核心在于如何通过一系列的区间加1操作,将给定数组a中的每个元素都转换为目标值w。特别地,每次选择的操作区间[l, r]需要满足l和r在所有操作中都是唯一的。
预处理:
- 首先计算出每一个位置上需要增加多少才能达到目标值w。即对每个元素执行a[i] = w - array[i]。
- 如果任意一个a[i] < 0,则直接返回0,因为不可能通过加法来减少数值。
- 接下来进行差分数组的构建:从后向前遍历数组,并且对于每个位置i,更新a[i] -= a[i-1]。这一步骤实际上是在考虑相邻元素之间的增量变化。如果在这个过程中发现有绝对值大于1的情况(除了第一个元素),则同样返回0,因为这意味着存在无法仅通过+1或-1调整到目标状态的情况。
动态规划:
- 定义一个二维DP数组f[i][j],其中i表示当前处理到了数组的第几个元素,而j表示目前剩余可用的不同右边界数量。
- 初始化条件根据初始a[0]的值设置:若为0,则f[0][0] = 1;若为1,则f[0][1] = 1。
- 动态转移方程如下:
- 当a[i] == 0时,说明当前位置不需要任何改变,因此可以从之前的状态继承过来。
- 若a[i] > 0,意味着必须使用一个新的右边界,所以从前一状态f[i-1][j-1]转移。
- 相反地,当a[i] < 0时,可以看作是撤销了一次之前的添加操作,因此可能来自f[i-1][j+1]。
输出结果:
最终答案取自f[n][0]或f[n][1]的最大值,这是因为最终我们希望所有的操作都已经完成,即没有剩余的未使用的右边界或者恰好有一个(用于最后一次操作)。
public class Main {
public static int solution(int n, int w, int[] array) {
ArrayList<Integer> a = new ArrayList<>();
for (int num : array) {
a.add(num);
}
for (int i = 0; i < a.size(); i++) {
a.set(i, w - a.get(i));
if (a.get(i) < 0) return 0;
}
a.add(0);
for (int i = n; i >= 1; i--) {
a.set(i, a.get(i) - a.get(i - 1));
if (Math.abs(a.get(i)) > 1) return 0;
}
if (Math.abs(a.get(0)) > 1) return 0;
int[][] f = new int[n + 1][n + 3];
if (a.get(0) == 1) f[0][1] = 1;
else if (a.get(0) == 0) f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= n + 1; j++) {
if (a.get(i) == 0) {
f[i][j] = f[i - 1][j] + j * f[i - 1][j];
}
if (j > 0 && a.get(i) > 0) {
f[i][j] = f[i - 1][j - 1];
}
if (j + 1 <= n + 1 && a.get(i) < 0) {
f[i][j] += f[i - 1][j + 1];
}
}
}
return Math.max(f[n][0], f[n][1]);
}
public static void main(String[] args) {
System.out.println(solution(2, 2, new int[]{1, 1}) == 2);
System.out.println(solution(3, 3, new int[]{1, 2, 3}) == 0);
}
}
总结分析
- 时间复杂度:O(n^2)。主要是由于双重循环结构导致的时间消耗,外层遍历整个数组长度n次,内层循环最多也进行n+1次迭代。
- 空间复杂度:O(n^2)。主要来自于维护的DP表f[][],其大小为(n+1)*(n+3),近似于O(n^2)。