如何降低时间复杂度来找到最长的锯齿序列

How to reduce the time complexity to find the longest zigzag sequence?

本文关键字:时间复杂度 何降低      更新时间:2023-10-16

我试图解决顶部编码器上的Z字形序列问题。我的代码的时间复杂度是O(n*n)。如何将其简化为O(n)或O(nlog(n))伪代码或算法的解释对我真的很有帮助这是问题陈述。问题陈述

如果连续数字之间的差在正负之间严格交替,则数字序列称为之字形序列。第一个差异(如果存在)可以是正的,也可以是负的。元素少于两个的序列通常是Z字形序列。

例如,1,7,4,9,5是一个Z字形序列,因为差值(6,-3,5,-7,3)交替为正和负。相反,1,4,7,2,5和1,7,4,5不是Z字形序列,第一个是因为它的前两个差是正的,第二个是因为最后一个差是零。

给定一个整数序列,sequence,返回锯齿形序列的最长子序列的长度。子序列是通过从原始序列中删除一定数量的元素(可能为零)而获得的,剩下的元素按其原始顺序排列。

这是我的代码

#include <iostream>
#include<vector>
#include<cstring>
#include<cstdio>
using namespace std;
class ZigZag
{
public:
int dp[200][2];
void print(int n)
{
for(int i=0;i<n;i++)
{
cout<<dp[i][0]<<endl;
}
}
int longestZigZag(vector<int> a)
{
int n=a.size();
//int dp[n][2];
for(int i=0;i<n;i++)
{
cout<<a[i]<<" "<<"t";
}
cout<<endl;
memset(dp,sizeof(dp),0);
dp[0][1]=dp[0][0]=1;
for(int i=1;i<n;i++)
{
dp[i][1]=dp[i][0]=1;
for(int j=0;j<i;j++)
{
if(a[i]<a[j])
{
dp[i][0]=max(dp[j][1]+1,dp[i][0]);
}
if(a[j]<a[i])
{
dp[i][1]=max(dp[j][0]+1,dp[i][1]);
}
}
cout<<dp[i][1]<<"t"<<dp[i][0]<<" "<<i<<endl;
//print(n);
}
cout<<dp[n-1][0]<<endl;
return max(dp[n-1][0],dp[n-1][1]);
}
};

U可以在O(n)中使用贪婪方法来实现。取第一个不重复的数字——这是锯齿形子序列的第一个数字。检查数组中的下一个数字是否小于或大于第一个数字。

情况1:如果小于,请检查该元素的下一个元素,并继续进行,直到找到最小的元素(即,之后的元素将大于前一个元素)。这将是你的第二个元素。

情况2:如果大于,请检查下一个元素,并继续进行,直到找到最大的元素(即,之后的元素将小于前一个元素)。这将是你的第二个元素。

如果您使用情况1查找第二个元素,则使用情况2查找第三个元素,反之亦然。在这两种情况之间保持交替,直到原始序列中没有更多元素为止。u得到的结果数将形成最长的Z字形子序列。

例如:{1,17,5,10,13,15,10,5,16,8}

生成的子序列:

1->1,17(病例2)->1,17,5(病例1)->1,1,7,5,15(病例2

因此,最长之字形子序列的长度为7。

U可以参考sjelkjd的解决方案来实现这个想法。

由于子序列不一定是连续的,所以不能使其为O(n)。在最坏的情况下,复杂性是O(2^n)。不过,我还是做了一些检查,以便尽快剪掉子树。

int maxLenght;
void test(vector<int>& a, int sign, int last, int pos, int currentLenght) {
if (maxLenght < currentLenght) maxLenght = currentLenght;
if (pos >= a.size() || pos >= a.size() + currentLenght - maxLenght) return;
if (last != a[pos] && (last - a[pos] >= 0) != sign) 
test(a,!sign,a[pos],pos+1,currentLenght+1);
test(a,sign,last,pos+1,currentLenght);
}
int longestZigZag(vector<int>& a) {
maxLenght = 0;
test(a,0,a[0],1,1);
test(a,!0,a[0],1,1);
return maxLenght;
}

您可以使用RMQ来移除内部for循环。当您找到dp[i][0]dp[i][1]的答案时,将其保存在两个RMQ树中——比如RMQ0和RMQ1——就像您现在对dp数组的两行所做的那样。因此,当您计算dp[i][0]时,您将值dp[i][0]放在RMQ0中的位置a[i]上,这意味着存在一个长度为dp[i][0]、以数字a[i]递增结尾的Z字形序列。

然后,为了计算dp[i + 1][0],您不必循环遍历0和i之间的所有数字。相反,您可以查询RMQ0以获取位置>a[i + 1]上的最大数字。这将为您提供最长的Z字形子序列,该子序列以一个大于当前数字的数字结尾,即可以用数字a[i + 1]递减继续的最长子序列。然后,您可以对RMQ1的另一半Z字形子序列执行同样的操作。

由于您可以实现查询复杂度为O(log N)的动态RMQ,因此这将为您提供O(N log N)的总体复杂度。

您可以在O(n)时间和O(n)额外空间内解决此问题。

算法如下。

  1. 将替换项的差异存储在大小为n-1的新数组中
  2. 现在遍历新数组,只需检查替换项的乘积是否小于零
  3. 相应地增加结果。如果在遍历时发现数组是乘积大于零,那么在这种情况下,您将存储结果,并再次开始计算差值数组中元素的其余部分
  4. 找出其中的最大值并存储到结果中,return (result+1)

这是它在C++中的实现

#include <iostream>
#include <vector>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
int n;
cin>>n;
vector<int> data(n);
for(int i = 0; i < n; i++)
cin>>data[i];
vector<int> diff(n-1);
for(int i = 1; i < n; i++)
diff[i-1] = data[i]-data[i-1];
int res = 1;
if( n < 2)
cout<<res<<"n";
else
{
int temp_idx = 0;
for(int i = 1; i < n-1; i++)
{
if(diff[i]*diff[i-1] < 0)
{
temp_idx++;
res++;
}
else
{
res = max(res,temp_idx);
temp_idx = 1;
}
}
cout<<res+1<<"n";
}
return 0;
}

这是一个纯粹的理论解决方案。如果你站在黑板旁边,在学术环境中被要求这样做,你就会解决这个问题

可以使用动态编程创建问题的解决方案:

子问题的形式是:如果我有序列的元素x,那么在该元素上结束的最长子序列是什么?

然后你可以使用递归调用来计算你的解决方案,它应该是这样的(关系的方向可能是错误的,我还没有检查):

S - given sequence (array of integers)
P(i), Q(i) - length of the longest zigzag subsequence on elements S[0 -> i] inclusive (the longest sequence that is correct, where S[i] is the last element)
P(i) = {if i == 0 then 1
{max(Q(j) if A[i] < A[j] for every 0 <= j < i)

Q(i) = {if i == 0 then 0  #yields 0 because we are pedantic about "is zig the first relation, or is it zag?". If we aren't, then this can be a 1.
{max(P(j) if A[i] > A[j] for every 0 <= j < i)

这应该是具有正确记忆的O(n)(存储Q(i)和p(i)的每个输出),因为每个子问题只计算一次:n*|P| + n*|Q|

这些调用返回解决方案的长度——只要找到最大值,就可以存储"父指针",然后在这些指针上向后遍历,从而找到实际结果。

您可以简单地通过用数组查找替换函数调用来避免递归:P[i]Q[i],并使用for循环。