【多维DP】【准NOI难度】力扣3251. 单调数组对的数目 II

news/2024/12/22 20:49:25 标签: leetcode, 算法, 数据结构

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

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

两个数组的长度都是 n 。
arr1 是单调 非递减 的,换句话说 arr1[0] <= arr1[1] <= … <= arr1[n - 1] 。
arr2 是单调 非递增 的,换句话说 arr2[0] >= arr2[1] >= … >= arr2[n - 1] 。
对于所有的 0 <= i <= n - 1 都有 arr1[i] + arr2[i] == nums[i] 。
请你返回所有 单调 数组对的数目。

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

示例 1:
输入:nums = [2,3,2]

输出:4

解释:

单调数组对包括:

([0, 1, 1], [2, 2, 1])
([0, 1, 2], [2, 2, 0])
([0, 2, 2], [2, 1, 0])
([1, 2, 2], [1, 1, 0])

示例 2:
输入:nums = [5,5,5,5]

输出:126

提示:
1 <= n == nums.length <= 2000
1 <= nums[i] <= 1000

动态规划

class Solution {
public:
    int countOfPairs(vector<int>& nums) {
        const int MOD = 1'000'000'007;
        int n = nums.size();
        int m = ranges::max(nums);
        vector f(n, vector<long long>(m + 1));
        vector<long long> s(m + 1);

        fill(f[0].begin(), f[0].begin() + nums[0] + 1, 1);
        for (int i = 1; i < n; i++) {
            partial_sum(f[i - 1].begin(), f[i - 1].end(), s.begin()); // f[i-1] 的前缀和
            for (int j = 0; j <= nums[i]; j++) {
                int max_k = j + min(nums[i - 1] - nums[i], 0);
                f[i][j] = max_k >= 0 ? s[max_k] % MOD : 0;
            }
        }

        return reduce(f[n - 1].begin(), f[n - 1].end(), 0LL) % MOD;
    }
};

时间复杂度:O(nm),其中 n 是 nums 的长度,m=max(nums)。
空间复杂度:O(nm)

这道题由于他要求arr1和arr2对应的arr1[i] + arr2[i] = nums[i],那么也就是说我们直到arr1的话,就可以知道arr2是多少。但是由于arr1是非递减的,而arr2是非递增的,所以arr2的范围不是单纯的nums[i]-arr1[i]。首先由于arr1是非递减的,那么也就是说arr1[i] >= arr1[i-1],然后arr2是非递增的,也就是说arr2[i] <= arr2[i-1]。我们假设arr1[i]为j,arr1[i-1]为k,arr2[i] = nums[i] - j而arr2[i-1] = nums[i-1] - k。那么我们可以列出算式nums[i-1] - k >= nums[i] - j。经过变换后得到k <= nums[i-1] - nums[i] + j。也就是说k的最大值就是nums[i-1] + nums[i] + j。而k的最大值在最小的情况下就是等于j,所以我们可以列出min(nums[i-1] - nums[i] + j, j),化简后就是int max_k = j + min(nums[i - 1] - nums[i], 0);

我们为什么要求最大k?原因是我们定义了一个二维数组f[i][j]用来表示索引0到索引i并且arr1[i]以j为结尾的单调数组对的数目。我们在递推过程中,我们遍历不同j为结尾,并且以j为结尾的单调组数目可以由f[i-1]的上一个状态并且不同结尾的数进行状态转换而来。那么我们知道了最大值k,我们就可以知道当前f[i][j]可以由f[i-1][0],f[i-1][1]…f[i-1][k]进行状态转移而来。为了避免状态转移过程中都计算f[i-1][0]+…+f[i-1][k]的值,我们可以计算上一个状态的前缀和,当我们知道最大的k的时候就可以直接使用。

由于arr1和arr2都是非负数,所以max_k必须大于0,f[i][j]才可以由上一个状态进行状态转移而来.

最后将f[i-1]的不同j为结尾的情况累加到一起就是答案。

该方法可以进行空间优化,这里不细说


还有一种O(n)的做法,是通过数学排列组合的方式,以后补充


http://www.niftyadmin.cn/n/5795850.html

相关文章

前端零基础学习Day-Eight

CSS字体和文本样式 CSS文字样式 字体&#xff1a;font-family 语法&#xff1a;font-family:[字体1][,字体2][,…] p{font-family:“微软雅黑”,“宋体”,“黑体”;} 含空格字体名和中文&#xff0c;用英文引号括起 属性值&#xff1a;具体字体名&#xff0c;字体集 字体集&…

【GD32】从零开始学GD32单片机 | DAC数模转换器 + 三角波输出例程

目录 简介输出缓冲外部触发数据转换噪声波LSFR噪声模式三角噪声模式 例程 简介 上一篇讲解了ADC的使用&#xff0c;所以这一篇讲DAC的使用&#xff0c;两者其实就是互补的关系&#xff0c;ADC将模拟信号转为数字信号&#xff0c;而DAC将数字信号转为模拟信号。具体的使用上DAC…

day11|150,239,347

150 其实不难&#xff0c;理解规律&#xff0c;遇到符号就需要提出来做运算。 class Solution {public int evalRPN(String[] tokens) {//向零截断&#xff0c;正数向下取整&#xff0c;负数向上取整//Queue<Integer> num new Queue<>()&#xff1b;是错的注意区…

数据结构:链表(经典算法例题)详解

目录 1.移除链表元素 2.反转链表 3.链表的中间结点 4.合并两个有序链表 5.环形链表的约瑟夫问题 6.分割链表 我以过客之名&#xff0c;祝你前程似锦 1.移除链表元素 &#xff08;1&#xff09;题目&#xff1a; https://leetcode.cn/problems/remove-linked-list-element…

Vue3 基础记录

Vue3 创建 基于vue-cli ## 查看vue/cli版本&#xff0c;确保vue/cli版本在4.5.0以上 vue --version## 安装或者升级你的vue/cli npm install -g vue/cli## 执行创建命令 vue create vue_test## 随后选择3.x ## Choose a version of Vue.js that you want to start the pr…

Golang学习历程【第三篇 基本数据类型类型转换】

Golang学习历程【第三篇 基本数据类型】 1. 总览2. 基本数据类型2.1 整型2.2 浮点型2.2 布尔型2.3 字符2.4 字符串2.4.1 常用定义方式2.4.2 转移字符2.4.3 常用方法2.4.3 字符串中字符替换 3. 类型转换3.1 整型与整型转化3.2 浮点数与整型转换3.3 其他类型与string类型转换3.4 …

亚马逊API接口深度解析:如何高效获取商品详情与评论数据

在当今数字化时代&#xff0c;电商平台的数据对于商家和开发者来说至关重要。亚马逊作为全球领先的电商平台&#xff0c;其API接口为开发者提供了丰富的商品信息和评论数据。本文将深入探讨如何使用亚马逊API接口获取商品详情和商品评论&#xff0c;同时提供简洁明了的使用方法…

代码随想录算法训练营第十一天-239.滑动窗口最大值

解题思想与代码实现&#xff0c;令人叹为观止队列的最佳应用从总体上讲&#xff0c;完成代码的思路是非常清晰的 根据窗口大小&#xff0c;从源数据第一个开始&#xff0c;把数据依次压入队列中从压入队列的数据中找出最大值&#xff0c;放入结果集合中再将队列中第一个元素弹出…