3202. 找出有效子序列的最大长度 II
题目描述
给你一个整数数组
返回
数据范围:
- 2 <= nums.length <=
- 1 <= nums[i] <=
- 1 <= k <=
思路1:巧妙的“暴力”
这是我赛时的思路,一种比较巧妙的暴力,就是写起来有点费力
观察数据范围,n和k都是1000,显然正确答案的复杂度应该类似于O(n * k),即1000 * 1000
由于题目的要求:相邻两个数求和对k去模后结果一样,所以我们可以考虑先枚举余数p
此时问题变成了,给定余数
比如,1 1 2 0 1 1 2 1 2 1 2,假设k是4,当前枚举的余数p是3,只要确定了起点1,那其对应的最长的子序列就是1 2 1 2 1 2 1 2,此时我们需要一种方法可以快速获得下标大于
虽然
我们可以再搞一个vis数组标记一下,因为我们通过跳转得到的一个子序列,从中间的任意一个元素开始跳都不会比起点开始更长,所以标记一下以后这些点就不会再重复计算了,当然还有一种情况是,存在一些中间多余的数字,比如1 1 2 1 1 2 1 2 1 2,最长的序列是1(1) 2 1 (1) 2 1 2 1 2,中间的两个1在跳转的过程中不会被遍历到,vis也不会标记它,此时id[1]和id[2]的位置也已经到了最后面,理论上从它开始跳转根本不会按正常的跳转方式得到一个完整的子序列,这种情况下是否会影响答案呢?
答案是不会影响的,看刚刚的例子,被省略的两个1,前面已经存在过1了,以1开头最长的子序列长度已经确定了,其他的1再开始也不会比他更长,所以是不会影响答案
不难发现,第二层看上去虽然是一层for一层while,但其实每个点只访问了一次,所以是O(n)的,所以这个代码的复杂度是O(n * k)
class Solution {
public:
int maximumLength(vector<int>& nums, int k) {
int tr[1005];
int n = nums.size();
vector<int>v[1005];
for(int i = 1; i <= n; ++i){
tr[i] = nums[i - 1];
tr[i] %= k;
v[tr[i]].push_back(i);
}
int ans = 0;
for(int p = 0; p < k; ++p){//枚举余数
bool vis[1005];
memset(vis, 0, sizeof(vis));
int id[1005];
memset(id, 0, sizeof(id));
for(int i = 1; i <= n; ++i){//枚举起点
if(vis[i])continue;
vis[i] = 1;
int num = 1;
int a = tr[i];
int pos = i;
while(1){
int b = (p - a + k) % k;
while(id[b] < v[b].size()){
if(v[b][id[b]] <= pos){
++id[b];
}
else break;
}
if(id[b] < v[b].size() && v[b][id[b]] > pos){
pos = v[b][id[b]];
vis[pos] = 1;
++num;
a = b;
}
else break;
}
ans = max(ans, num);
}
}
return ans;
}
};
思路2:dp
确定了余数p和第一个数,这个序列就确定了,会是两个数字循环
所以我们可以考虑用
转移方程很简单,
class Solution {
public:
int maximumLength(vector<int>& nums, int k) {
int ans = 0;
for(int p = 0; p < k; ++p){
vector<int>dp(k, 0);
for(int i = 0; i < nums.size(); ++i){
nums[i] %= k;
dp[nums[i]] = dp[(p - nums[i] + k) % k] + 1;
ans = max(ans, dp[nums[i]]);
}
}
return ans;
}
};