leetcode 第133场双周赛 100333.统计逆序对的数目【计数dp/滚动数组/前缀和优化】


分析:

先考虑如下问题。

求长度为n,逆序对为m的排列数量。

可以考虑dpdp[i][j]定义为长度为i,逆序对为j的排列数量。

cpp 复制代码
dp[1][0] = 1;
//枚举排列长度,或者认为枚举当前需要插到长度为i-1的排列中的数字
for(int i = 1; i <= n; ++i)  
{
    for(int j = 0; j <= i * (i + 1) / 2; ++j)
    {
        //枚举当前数字插到的位置,一共i个位置,分别可能使逆序对增加0~i-1个
        for(int k = 0; k < i; ++k)
        {
            if(j >= k)
            {
                dp[i][j] += dp[i - 1][j - k];
            }
        }
    }
}

在懂了上述dp之后,再来考虑本题。主要有两个问题。

  • 另外考虑,如果dp[i][j]是否依旧可以这样求,因为上述问题中对于长度为i的排列,前i-1个数字确定的,i一定是最大的,我们只需要考虑它放在哪个位置即可。
  • 如何同时满足requirements[i][endi] = cntirequirements[j][endj] = cntj,其中i!=j

对于第一点,肯定是可以这样求的,一方面我们不需要关心前i-1个数字是什么,只需要认为我们枚举的第i个数字是这i个数字中最大的(类似上述思路)或者是最小的(与最大的等效并且更加方便理解上述dp的最内层循环)即可,另一方面我们看到至少有一个i满足endi == n - 1

对于第二点,我们只需要在dp的过程中适当修改。若 ∃ e n d j = = i \exists end_j == i ∃endj==i,则正常求 d p [ i ] [ c n t j ] dp[i][cnt_j] dp[i][cntj]的值,而 d p [ i ] [ k ] = 0 , k ≠ c n t j dp[i][k]=0,k\ne cnt_j dp[i][k]=0,k=cntj。

AC代码

cpp 复制代码
class Solution {
public:
    int numberOfPermutations(int n, vector<vector<int>>& requirements) {
        
        const int mod = 1e9 + 7;
        
        vector<int> vt(305, -1);
        for(auto x: requirements) vt[x[0] + 1] = x[1];

        vector<vector<int>> dp(305, vector<int>(405, 0));
        dp[0][0] = 1;

        for (int i = 1; i <= n; ++i) 
        {
            if (vt[i] != -1) 
            {
                int j = vt[i];
                for (int k = 0; k < i; ++k) if (j >= k) dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % mod;
                continue;
            }
            for (int j = 0; j <= min(400, (1 + i) * i / 2); ++j) 
            {
                for (int k = 0; k < i; ++k) 
                {
                    if (j >= k) dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % mod;
                }
            }
        }
        
        return dp[n][vt[n]];
    }
};
//dp数组定义为vector,如果定义为数组,一定记得先memset 0
//(dp[i][j] += dp[i - 1][j - k]) % mod不等价于dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % mod;
//(dp[i][j] += dp[i - 1][j - k]) % mod,模完之后值未赋给任何数。

上述代码已经可以AC,但是可以进一步优化

  • dp[i][j]的递推过程中,只用到了dp[i-1][j-k],故可以通过滚动数组优化空间。
  • 对于最内层枚举k的循环,我们发现递推公式等价于 d p [ i ] [ j ] = ∑ k = j − ( i − 1 ) j d p [ i − 1 ] [ k ] dp[i][j] = \sum_{k=j-(i-1)}^{j} dp[i-1][k] dp[i][j]=∑k=j−(i−1)jdp[i−1][k],即是dp[i-1]数组的一个前缀和,故可以预处理出前缀和,使得dp[i][j]实现O(1)递推,优化为两层循环。

优化后的代码:

cpp 复制代码
class Solution {
public:
    int numberOfPermutations(int n, vector<vector<int>>& requirements) {
        
        const int mod = 1e9 + 7;
        
        vector<int> vt(305, -1);
        for(auto x: requirements) vt[x[0] + 1] = x[1];

        vector<int> dp(405, 0);
        vector<int> sum(405, 0);
        dp[0] = 1;

        for (int i = 1; i <= n; ++i) 
        {
            sum[0] = dp[0];
            for(int j = 1; j <= min(400, (1 + i) * i / 2); ++j) sum[j] = (sum[j - 1] + dp[j]) % mod;
            if (vt[i] != -1) 
            {
                for(int j = 0; j <= min(400, (1 + i) * i / 2); ++j) dp[j] = 0;
                int j = vt[i];
                if(j < i) dp[j] = sum[j];
                else dp[j] = (sum[j] - sum[j - i] + mod) % mod;
                continue;
            }
            for (int j = 0; j <= min(400, (1 + i) * i / 2); ++j) 
            {
                if(j < i) dp[j] = sum[j];
                else dp[j] = (sum[j] - sum[j - i] + mod) % mod;
            }
        }
        
        return dp[vt[n]];
    }
};
相关推荐
聚客AI1 天前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm
大怪v2 天前
前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!
前端·javascript·算法
惯导马工2 天前
【论文导读】ORB-SLAM3:An Accurate Open-Source Library for Visual, Visual-Inertial and
深度学习·算法
骑自行车的码农2 天前
【React用到的一些算法】游标和栈
算法·react.js
博笙困了2 天前
AcWing学习——双指针算法
c++·算法
moonlifesudo2 天前
322:零钱兑换(三种方法)
算法
NAGNIP2 天前
大模型框架性能优化策略:延迟、吞吐量与成本权衡
算法
美团技术团队3 天前
LongCat-Flash:如何使用 SGLang 部署美团 Agentic 模型
人工智能·算法
Fanxt_Ja3 天前
【LeetCode】算法详解#15 ---环形链表II
数据结构·算法·leetcode·链表
侃侃_天下3 天前
最终的信号类
开发语言·c++·算法