小M的幸运数列变换|豆包MarsCode AI刷题

64 阅读3分钟

问题描述

小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)。