本文涉及知识点
动态规划 状态机dp 性能优化
leetcode3098. 求出所有子序列的能量和
给你一个长度为 n 的整数数组 nums 和一个 正 整数 k 。
一个子序列的 能量 定义为子序列中 任意 两个元素的差值绝对值的 最小值 。
请你返回 nums 中长度 等于 k 的 所有 子序列的 能量和 。
由于答案可能会很大,将答案对 109 + 7 取余 后返回。
示例 1:
输入:nums = [1,2,3,4], k = 3
输出:4
解释:
nums 中总共有 4 个长度为 3 的子序列:[1,2,3] ,[1,3,4] ,[1,2,4] 和 [2,3,4] 。能量和为 |2 - 3| + |3 - 4| + |2 - 1| + |3 - 4| = 4 。
示例 2:
输入:nums = [2,2], k = 2
输出:0
解释:
nums 中唯一一个长度为 2 的子序列是 [2,2] 。能量和为 |2 - 2| = 0 。
示例 3:
输入:nums = [4,3,-1], k = 2
输出:10
解释:
nums 总共有 3 个长度为 2 的子序列:[4,3] ,[4,-1] 和 [3,-1] 。能量和为 |4 - 3| + |4 - (-1)| + |3 - (-1)| = 10 。
提示:
2 <= n == nums.length <= 50
-108 <= nums[i] <= 108
2 <= k <= n
动态规划(状态机dp)初版
动态规划的状态 表示
pre 表示已经处理完前x个数组符合条件的数量,dp表示已经处理完x+1数组符合条件的数量。
pre[i][j][end][len] 表示此子序列:
a,长度为len。
b,以nums[end]结束。
c,nums[j]-nums[i]的差最小。如果多个(i,j)符合条件,取最小的。比如:{1,2,3}的(i,j)是{0,1}而不是{1,2}。
空间复杂度:o(nnnk)
dp类似。
动态规划的转移方程
只需要从x 推导x+1,不需要推导x+2,x+3
⋯
\cdots
⋯ ,如果硬要的话需要用前缀和(后缀和)。
{
d
p
=
p
r
e
不选择
n
u
m
s
[
x
]
d
p
[
i
]
[
j
]
[
x
]
[
l
e
n
+
1
]
+
=
.
.
.
e
l
s
e
且
n
u
m
s
[
j
]
−
n
u
m
s
[
i
]
<
=
n
u
m
s
[
x
]
−
n
u
m
s
[
e
n
d
]
d
p
[
e
n
d
]
[
x
]
[
x
]
[
l
e
n
+
1
]
+
=
.
.
.
e
l
s
e
\begin{cases} dp = pre && 不选择nums[x] \\ dp[i][j][x][len+1] += ... && else 且 nums[j]-nums[i] <= nums[x]-nums[end] \\ dp[end][x][x][len+1] += ... else \\ \end{cases}
⎩
⎨
⎧dp=predp[i][j][x][len+1]+=...dp[end][x][x][len+1]+=...else不选择nums[x]else且nums[j]−nums[i]<=nums[x]−nums[end]
时间复杂度:o(nnnkn) 估计超时
剪枝:
枚举的时候确保 i < j ,且 j <= x。
动态规划+前缀和
拆分成若干个子问题,假定序列存在(i,j),且此序列的能力为power = nums[j]-nums[i]。
动态规划的状态表示
dp[len][end] 表示 子序列的长度为len,最后一个元素是end。
空间复杂度:o(kn)
利用前缀和优化 动态规划的转移方程
枚举end,end not
∈
\in
∈(i,j) ,否则此序列的能量就不是nums[j]-nums[i]了。
{
o
l
d
e
n
d
∈
[
0
,
e
n
d
)
且
n
u
m
s
[
e
n
d
]
−
n
u
m
s
[
o
l
d
e
n
d
]
>
p
o
w
e
r
e
n
d
<
=
i
o
e
d
e
n
d
∈
(
e
n
d
,
n
)
且
n
u
m
s
[
e
n
d
]
−
n
u
m
s
[
o
l
d
e
n
d
]
>
=
p
o
w
e
r
e
n
d
>
=
j
\begin{cases} oldend \in [0,end)且nums[end] -nums[oldend] > power && end <= i \\ oedend \in (end,n) 且 nums[end] -nums[oldend] >= power && end >=j \\ \end{cases}
{oldend∈[0,end)且nums[end]−nums[oldend]>poweroedend∈(end,n)且nums[end]−nums[oldend]>=powerend<=iend>=j
如果不利用前缀和优先,时间复杂度:o(knn),利用前缀和优化o(kn)。
总时间复杂度:o(knkn)。
动态规划的初始状态
枚举所有长度为2
动态规划的填表顺序
l e n = 3 n _{len=3}^{n} len=3n
动态规划的返回值
len == k 且 end >=j 才是需要统计的子序列数量。
代码
没用前缀和优化
理论上过不了,实际过了。
template<int mod = 1000000007>
class c1097int
{
public:
c1097int(long long lldata = 0) :m_idata(lldata% mod)
{
}
c1097int operator+(const c1097int& o)const
{
return c1097int(((long long)m_idata + o.m_idata) % mod);
}
c1097int& operator+=(const c1097int& o)
{
m_idata = ((long long)m_idata + o.m_idata) % mod;
return *this;
}
c1097int& operator-=(const c1097int& o)
{
m_idata = (m_idata + mod - o.m_idata) % mod;
return *this;
}
c1097int operator-(const c1097int& o)
{
return c1097int((m_idata + mod - o.m_idata) % mod);
}
c1097int operator*(const c1097int& o)const
{
return((long long)m_idata * o.m_idata) % mod;
}
c1097int& operator*=(const c1097int& o)
{
m_idata = ((long long)m_idata * o.m_idata) % mod;
return *this;
}
bool operator==(const c1097int& o)const
{
return m_idata == o.m_idata;
}
bool operator<(const c1097int& o)const
{
return m_idata < o.m_idata;
}
c1097int pow(long long n)const
{
c1097int iret = 1, icur = *this;
while (n)
{
if (n & 1)
{
iret *= icur;
}
icur *= icur;
n >>= 1;
}
return iret;
}
c1097int pownegative1()const
{
return pow(mod - 2);
}
int toint()const
{
return m_idata;
}
private:
int m_idata = 0;;
};
class solution {
public:
int sumofpowers(vector<int>& nums, const int k) {
m_c = nums.size();
sort(nums.begin(), nums.end());
c1097int<> biret = 0;
for (int i = 0; i < m_c; i++) {
for (int j = i + 1; j < m_c; j++) {
auto cur = do(nums, i, j, k);
biret += cur;
//std::cout << " i :" << i << " j:" << j << " " << cur.toint() << std::endl;
}
}
return biret.toint();
}
c1097int<> do(const vector<int>& nums,int i,int j, const int k) {
const int idiff = nums[j] - nums[i];
vector<vector<c1097int<>>> dp(k + 1, vector<c1097int<>>(m_c));
for (int end = 0; end <= i; end++) {
for (int end1 = 0; end1 < end; end1++) {
if (nums[end] - nums[end1] > idiff) {
dp[2][end] += 1;
}
}
}
dp[2][j] = 1;
for (int len = 3; len <= k; len++) {
for (int end = 0; end <= i; end++) {
for (int end1 = 0; end1 < end; end1++) {
if (nums[end] - nums[end1] > idiff) {
dp[len][end] += dp[len - 1][end1];
}
}
}
dp[len][j] = dp[len - 1][i];
for (int end = j+1; end < m_c; end++) {
for (int end1 = j; end1 < end; end1++) {
if (nums[end] - nums[end1] >= idiff) {
dp[len][end] += dp[len - 1][end1];
}
}
}
}
return std::accumulate(dp.back().begin() + j, dp.back().end(), c1097int<>())*idiff;
}
int m_c;
};
测试用例
int main()
{
vector<int> nums;
int k;
{
solution sln;
nums = { 6,14,4,13 }, k = 3;
auto res = sln.sumofpowers(nums, k);
assert(6, res);
}
{
solution sln;
nums = { 1,2,3,4 }, k = 3;
auto res = sln.sumofpowers(nums, k);
assert(4, res);
}
{
solution sln;
nums = { 4,3,-1 }, k = 2;
auto res = sln.sumofpowers(nums, k);
assert(10, res);
}
{
solution sln;
nums = { 2,2 }, k = 2;
auto res = sln.sumofpowers(nums, k);
assert(0, res);
}
{
solution sln;
nums = { 2,246006,496910,752786,1013762,1279948,1551454,1828436,2110982,2399316,2693558,2993942,3300640,3613766,3933442,4259696,4592656,4932556,5279494,5633522,5994678,6363102,6739028,7122528,7513792,7913044,8320394,8736004,9160062,9592750,10034184,10484602,10944108,11412852,11891048,12378822,12876346,13383746,13901098,14428528,14966126,15514010,16072380,16641300,17220904,17811360,18412850,19025600,19649778,20285440 }, k = 37;
auto res = sln.sumofpowers(nums, k);
assert(273504325, res);
}
}
利用前缀和优化:用时减少不到50%
template<int mod = 1000000007>
class c1097int
{
public:
c1097int(long long lldata = 0) :m_idata(lldata% mod)
{
}
c1097int operator+(const c1097int& o)const
{
return c1097int(((long long)m_idata + o.m_idata) % mod);
}
c1097int& operator+=(const c1097int& o)
{
m_idata = ((long long)m_idata + o.m_idata) % mod;
return *this;
}
c1097int& operator-=(const c1097int& o)
{
m_idata = (m_idata + mod - o.m_idata) % mod;
return *this;
}
c1097int operator-(const c1097int& o)
{
return c1097int((m_idata + mod - o.m_idata) % mod);
}
c1097int operator*(const c1097int& o)const
{
return((long long)m_idata * o.m_idata) % mod;
}
c1097int& operator*=(const c1097int& o)
{
m_idata = ((long long)m_idata * o.m_idata) % mod;
return *this;
}
bool operator==(const c1097int& o)const
{
return m_idata == o.m_idata;
}
bool operator<(const c1097int& o)const
{
return m_idata < o.m_idata;
}
c1097int pow(long long n)const
{
c1097int iret = 1, icur = *this;
while (n)
{
if (n & 1)
{
iret *= icur;
}
icur *= icur;
n >>= 1;
}
return iret;
}
c1097int pownegative1()const
{
return pow(mod - 2);
}
int toint()const
{
return m_idata;
}
private:
int m_idata = 0;;
};
class solution {
public:
int sumofpowers(vector<int>& nums, const int k) {
m_c = nums.size();
sort(nums.begin(), nums.end());
c1097int<> biret = 0;
for (int i = 0; i < m_c; i++) {
for (int j = i + 1; j < m_c; j++) {
auto cur = do(nums, i, j, k);
biret += cur;
//std::cout << " i :" << i << " j:" << j << " " << cur.toint() << std::endl;
}
}
return biret.toint();
}
c1097int<> do(const vector<int>& nums, int i, int j, const int k) {
const int idiff = nums[j] - nums[i];
vector<vector<c1097int<>>> dp(k + 1, vector<c1097int<>>(m_c));
for (int end = 0; end <= i; end++) {
for (int end1 = 0; end1 < end; end1++) {
if (nums[end] - nums[end1] > idiff) {
dp[2][end] += 1;
}
}
}
dp[2][j] = 1;
for (int len = 3; len <= k; len++) {
int end1 = 0;
c1097int<> biret = 0;
for (int end = 0; end <= i; end++) {
while ((end1 < end) && (nums[end] - nums[end1] > idiff)) {
biret += dp[len - 1][end1];
end1++;
}
dp[len ][end] = biret;
}
dp[len][j] = dp[len - 1][i];
c1097int<> biret2 = 0;
for (int end = j + 1,end1=j ; end < m_c; end++) {
while ((end1 < end) && (nums[end] - nums[end1] >= idiff)) {
biret2 += dp[len - 1][end1];
end1++;
}
dp[len][end] = biret2;
}
}
return std::accumulate(dp.back().begin() + j, dp.back().end(), c1097int<>()) * idiff;
}
int m_c;
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步csdn学院,听白银讲师(也就是鄙人)的讲解。
如何你想快速形成战斗了,为老板分忧,请学习c#入职培训、c++入职培训等课程
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
我想对大家说的话 |
---|
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: vs2019 c++17
或者 操作系统:win10 开发环境: vs2022 c++17
如无特殊说明,本算法用**c++**实现。
发表评论