LeetCode 629 - K Inverse Pairs Array
题目描述
[HARD]
For an integer array nums, an inverse pair is a pair of integers [i, j] where 0 <= i < j < nums.length and nums[i] > nums[j].
Given two integers n and k, return the number of different arrays consisting of numbers from 1 to n such that there are exactly k inverse pairs. Since the answer can be huge, return it modulo 10^9 + 7.
Example 1:
1 | Input: n = 3, k = 0 |
Example 2:
1 | Input: n = 3, k = 1 |
思路
1 问题建模与 DP 状态
定义
令 dp[i][j] 表示:用数字 1..i 组成的所有排列中,恰好有 j 个逆序对的排列个数。
目标
求 dp[n][k],并对 1e9+7 取模。
直觉
从 i-1 个数扩展到 i 个数时,把新数 i 插入到某个位置。由于 i 是当前最大的数,它与它右边的每一个元素都形成一个逆序对。因此,若把 i 放到从右数的第 x 个位置(也就是在末尾右侧有 x 个元素在它右边),就会新增加 x 个逆序对,其中 x ∈ [0, i-1]。
于是:
$$
dp[i][j] = \sum_{x=0}^{min(j, i-1)} dp[i-1][j - x]
$$
这就是核心转移。
2 优化
前缀和优化
直接按上式求和,每个 dp[i][j] 要枚举 x,代价是 O(i),总体会到 O(n * k * n)(或最坏到 O(n * k * k)),会超时。
观察到这其实是一个滑动窗口和:
当 j < i 时,窗口是 [0..j],
dp[i][j] = dp[i-1][j] + dp[i-1][j-1] + … + dp[i-1][0]
当 j ≥ i 时,窗口是长度为 i 的区间 [j-(i-1) .. j],
dp[i][j] = dp[i-1][j] + dp[i-1][j-1] + … + dp[i-1][j-(i-1)]
用前缀和 prefix[j] = dp[i-1][0] + … + dp[i-1][j],就能 O(1) 得到区间和:
j < i:dp[i][j] = prefix[j]
j ≥ i:dp[i][j] = prefix[j] - prefix[j - i]
这正是代码里两段分支的含义。
为防止 C++ 负数取模的问题,代码使用
1 | ((prefix[j] - prefix[j-i]) % MOD + MOD) % MOD |
先加一次 MOD 再 %MOD,保证非负。
代码
1 | class Solution { |