0%

3177. 求出最长好子序列 II

3177.求出最长好子序列II

题目描述:

给你一个整数数组 nums 和一个 非负 整数 k 。如果一个整数序列 seq 满足在范围下标范围 [0, seq.length - 2] 中存在 不超过 k 个下标 i 满足 seq[i] != seq[i + 1] ,那么我们称这个整数序列为 序列。

请你返回 nums 子序列 的最长长度

数据范围:

$1\le nums.len \le 5 \times 10^3, 1\le nums[i] \le 10^9, 0\le k \le \min(50, nums.len)$

题解:

子序列,不是子数组。子数组的话可以直接滑动窗口,子序列只能dp了。

考虑 $dp[i][k]$ 表示以 $nums[i]$ 结尾,且不相等的个数为 $k$ 的最大长度。

则可以得到

但是复杂度为 $O(n^2\times k)$ ,会超时。

考虑求前缀最大值优化。优化掉 $j$ 这层循环。

$dp[i][k]$ 需要使用 $dp[j][k - 1], dp[j][k]$ 也就是前一列的最大值,和当前列的最大值。 $k$ 从前往后,需要每一列的最大值。可以用一个长度为 $k + 1$ 的数组维护每一列的最大值。然后当遍历完一行之后,更新一下每列的最大值。

需要注意的是, $dp[j][k]$ 怎么优化掉?

因为 $dp[j][k]$ 只会在 $nums[j] = nums[i]$ 时出现,而且相等时肯定选最新的。因此可以使用一个 unordered_map ,直接把上一行拷贝过来。

然后再遍历所有的不相等的情况。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
auto optimize_cpp_stdio = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
class Solution
{
public:
const static int maxn = 1e5 + 10;
const static int maxm = 1e5 + 10;
const static long long mod = 1e9 + 7;
const long long INF_LL = 0x3f3f3f3f3f3f3f3f;
const int INF = 0x3f3f3f3f;
int maximumLength(vector<int> &nums, int k)
{
int n = nums.size();
vector<vector<int>> dp(n + 1, vector<int>(k + 1));
// dp[i][k] 表示 以 nums[i] 结尾的,刚好为 k 个的长度
for (int i = 0; i < n; ++i)
{
dp[i][0] = 1;
}
int ans = 1;
for (int i = 1; i < n; ++i)
{
for (int kk = 0; kk <= min(k, i + 1); ++kk)
{
for (int j = 0; j < i; ++j)
{
if (nums[i] == nums[j])
{
dp[i][kk] = max(dp[i][kk], dp[j][kk] + 1);
}
else
{
// 前一列的最大值,i 往后移动,需要维护每一列的最大值
if (kk - 1 >= 0)
dp[i][kk] = max(dp[i][kk], dp[j][kk - 1] + 1);
}
}
ans = max(ans, dp[i][kk]);
}
}
return ans;
}
};