【Leetcode 每日一题】3250. 单调数组对的数目 I

问题背景

给你一个长度为 n n n 的 正 整数数组 n u m s nums nums。

如果两个 非负 整数数组 ( a r r 1 , a r r 2 ) (arr_1, arr_2) (arr1,arr2) 满足以下条件,我们称它们是 单调 数组对:

两个数组的长度都是 n n n。

  • a r r 1 arr_1 arr1 是单调 非递减 的,换句话说 a r r 1 0 ≤ a r r 1 1 ≤ . . . ≤ a r r 1 n − 1 arr_10 \le arr_11 \le ... \le arr_1n - 1 arr10≤arr11≤...≤arr1n−1。
  • a r r 2 arr_2 arr2 是单调 非递增 的,换句话说 a r r 2 0 ≤ a r r 2 1 ≤ . . . ≤ a r r 2 n − 1 arr_20 \le arr_21 \le ... \le arr_2n - 1 arr20≤arr21≤...≤arr2n−1。
  • 对于所有的 0 ≤ i ≤ n − 1 0 \le i \le n - 1 0≤i≤n−1 都有 a r r 1 i + a r r 2 i = = n u m s i arr_1i + arr_2i == numsi arr1i+arr2i==numsi。

请你返回所有 单调 数组对的数目。

由于答案可能很大,请你将它对 1 0 9 + 7 10 ^ 9 + 7 109+7 取余 后返回。

数据约束

  • 1 ≤ n = n u m s . l e n g t h ≤ 2000 1 \le n = nums.length \le 2000 1≤n=nums.length≤2000
  • 1 ≤ n u m s i ≤ 50 1 \le numsi \le 50 1≤numsi≤50

解题过程

周赛三四题的水准,两道题目只在数据规模上有差异,目前只能尝试写灵神题解的解释。

这题虽然对两个数组有要求,但是实际上只需要枚举其中一个数组的情况,把对另外一个数组中元素的要求当成约束就行。

根据动态规划缩小问题规模的思想:

  • 原问题是下标 0 0 0 到 n − 1 n - 1 n−1中的单调数组对的个数,且 a r r 1 n − 1 = j = 0 , 1 , 2 , . . . , n u m s n − 1 arr_1n−1 = j = 0, 1, 2, ..., numsn - 1 arr1n−1=j=0,1,2,...,numsn−1。
  • 子问题是下标 0 0 0 到 i i i 中的单调数组对的个数,且 a r r 1 i = j arr_1i = j arr1i=j,将其记作 d p i j dpij dpij。

用 k k k表示 a r r 1 i − 1 arr_1i−1 arr1i−1,那么根据约束条件: { a r r 1 i − 1 ≤ a r r 1 i a r r 2 i − 1 ≥ a r r 2 i \ \begin{cases} arr_1i - 1 \le arr_1i \\ arr_2i - 1 \ge arr_2i \\ \end{cases} {arr1i−1≤arr1iarr2i−1≥arr2i,即 { k ≤ j n u m s i − 1 − k ≥ n u m s i − j \ \begin{cases} k \le j \\ numsi - 1 - k \ge numsi - j \\ \end{cases} {k≤jnumsi−1−k≥numsi−j,可以得到 k k k 的上界。

解得 k ≤ m i n ( j , n u m s i − 1 − n u m s i + j ) = j + m i n ( n u m s i − 1 − n u m s i , 0 ) k \le min(j, numsi - 1 - numsi + j) = j + min(numsi - 1 - numsi, 0) k≤min(j,numsi−1−numsi+j)=j+min(numsi−1−numsi,0),由于所有数组中的元素都是非负的,而 n u m s i = a r r 1 i + a r r 2 i numsi = arr_1i + arr_2i numsi=arr1i+arr2i,所以 k ≤ n u m s i − 1 k \le numsi - 1 k≤numsi−1。

记 m a x K = j + m i n ( n u m s i − 1 − n u m s i , 0 ) maxK = j + min(numsi - 1 - numsi, 0) maxK=j+min(numsi−1−numsi,0),那么 d p i j = { 0 , m a x K < 0 Σ k = 0 m a x K d p i − 1 k , m a x K ≥ 0 dpij = \begin{cases} 0,& maxK \lt 0 \\ \mathop{\Sigma} \limits _ {k = 0} ^ {maxK} dpi - 1k,& maxK \ge 0 \\ \end{cases} dpij=⎩ ⎨ ⎧0,k=0ΣmaxKdpi−1k,maxK<0maxK≥0。

这里 Σ k = 0 m a x K d p i − 1 k \mathop{\Sigma} \limits _ {k = 0} ^ {maxK} dpi - 1k k=0ΣmaxKdpi−1k 表示对所有的 k k k 从 0 0 0 到 m a x K maxK maxK 的情况,求下标 0 0 0 到 i − 1 i - 1 i−1 中的单调数组对的个数之和,要求 a r r 1 = k arr_1 = k arr1=k。这显然满足前缀和的定义,记 s j = Σ k = 0 j d p i − 1 k sj = \mathop{\Sigma} \limits _ {k = 0} ^ {j} dpi - 1k sj=k=0Σjdpi−1k,那么上述状态转移方程 ( 3 ) (3) (3) 可以简化为 d p i j = { 0 , m a x K < 0 s m a x K , m a x K ≥ 0 dpij = \begin{cases} 0,& maxK \lt 0 \\ smaxK,& maxK \ge 0 \\ \end{cases} dpij={0,smaxK,maxK<0maxK≥0。

初始值 d p 0 j = 1 dp0j = 1 dp0j=1,其中 j = 0 , 1 , 2 , . . . , n u m s 0 j = 0, 1, 2, ..., nums0 j=0,1,2,...,nums0。这表示的是,下标 0 0 0 到 0 0 0 中的单调数组对的个数,也就是只考虑数组中第一个元素的情况, a r r 1 i arr_1i arr1i 可以是合法范围内任意值。

后续还有进一步优化,目前一下子掌握不了,先暂时放弃。

具体实现

java 复制代码
class Solution {

    // 根据题意定义模数
    private static final int MOD = 1000000007;

    public int countOfPairs(int[] nums) {
        int n = nums.length;
        // m 表示 nums 数组中的最大值,所有数组中的元素都不会超过这个范围,也就是应枚举的最大值
        int m = Arrays.stream(nums).max().getAsInt();
        long [][] dp = new long[n][m + 1];
        long[] preSum = new long[m + 1];
        // 填充初始值,只考虑数组中第一个元素的情况
        Arrays.fill(dp[0], 0, nums[0] + 1, 1);
        for(int i = 1; i < n; i++) {
            // 计算前缀和,这是它的定义
            preSum[0] = dp[i - 1][0];
            for(int k = 1; k <= m; k++) {
                preSum[k] = (preSum[k - 1] + dp[i - 1][k]) % MOD;
            }
            for(int j = 0; j <= nums[i]; j++) {
                int maxK = j + Math.min(nums[i - 1] - nums[i], 0);
                dp[i][j] = maxK >= 0 ? preSum[maxK] % MOD : 0;
            }
        }
        // 答案是考虑完数组中最后一个元素之后,对所有可能情形求和
        return (int) (Arrays.stream(dp[n - 1], 0, nums[n - 1] + 1).sum() % MOD);
    }
}
相关推荐
mmmmath_32 小时前
LeetCode.199.二叉树的右视图
数据结构·leetcode
专注仿真2 小时前
引擎扩展性与集成设计深度剖析:从 14 个扩展点到 DIS→HLA 演进之路
开发语言·算法·仿真引擎·推演引擎
码爸2 小时前
支持向量机介绍与示例
算法·机器学习·支持向量机
怕浪猫3 小时前
2028编程圈很火的10个Skills
算法·面试·github
Nil2087 小时前
leetcode 198打家劫舍
算法·leetcode·职场和发展
Shaoshing7 小时前
前缀和与差分
java·数据结构·算法·前缀和·差分
mmmmath_38 小时前
LeetCode.429.N叉树的层序遍历
数据结构·leetcode
loulanyue_8 小时前
构建房产认知决策智能:Harness底座与AI自进化的技术实践——读贝壳首席算法科学家尹俊希2026云栖专场演讲
人工智能·算法
碧海银沙音频科技研究院8 小时前
基于炬芯ATS录音卡BLE控制协议
人工智能·嵌入式硬件·算法·语音识别
suhan9 小时前
【数据结构】三、栈和队列:6.链队列、双端队列、队列的应用(树的层次遍历、广度优先BFS、先来先服务FCFS)
数据结构·后端·算法