如何减少时间复杂度,找到最长的锯齿形序列?

时间:2012-10-14 08:50:27

标签: c++ algorithm sequence dynamic-programming

我试图解决顶级编码器上的问题zig zag序列。我的代码的时间复杂度是O(n * n)。如何将其减少为O(n)或O(nlog(n)) 伪代码或算法的解释对我很有帮助 这是问题陈述。 问题陈述

  

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

     

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

     

给定一系列整数序列,返回序列的最长子序列的长度,该序列是Z字形序列。通过从原始序列中删除一些元素(可能为零)来获得子序列,将剩余的元素保留为原始顺序。

这是我的代码

#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]);
  }
};

5 个答案:

答案 0 :(得分:5)

U可以使用贪婪方法在 O(n)中执行此操作。取第一个不重复的数字 - 这是你的之字形子序列的第一个数字。检查阵列中的下一个数字是否小于或大于第一个数字。

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

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

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

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

结果子序列:

1 - &gt; 1,17(案例2) - &gt; 1,17,5(案例1) - &gt; 1,17,5,15(案例2) - &gt; 1,17,5,15,5(案例1) - &gt; 1,17,5,15,5,16(案例2) - &gt; 1,17,5,15,5,16,8(案例1)

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

你可以参考sjelkjd的solution来实现这个想法。

答案 1 :(得分:1)

由于子序列不一定是连续的,你不能使它成为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;
}

答案 2 :(得分:0)

您可以使用RMQs删除内部for循环。当您找到dp[i][0]dp[i][1]的答案时,请将其保存在两个RMQ树中 - 例如,RMQ 0 和RMQ 1 - 就像您一样现在正在使用dp数组的两行。因此,当您计算dp[i][0]时,将值dp[i][0]放在RMQ 0 中的位置a[i]上,这意味着存在长度为{的Z字形序列{ {1}}越来越多地以数字dp[i][0]结束。

然后,为了计算a[i],您不必遍历0和dp[i + 1][0]之间的所有数字。相反,您可以查询RMQ 0 以获取位置上的最大数字&gt; i。这将为您提供最长的Z字形子序列,其末尾的数字大于当前的数字 - 即可以用数字a[i + 1]递减的最长的一个。然后你可以为RMQ 1 做另外一半的zig-zag子序列。

由于您可以实现查询复杂度为a[i + 1]的动态RMQ,因此整体复杂度为O(log N)

答案 3 :(得分:0)

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

算法如下。

  1. 将替代字词的差异存储在大小为n-1
  2. 的新数组中
  3. 现在遍历新阵列,只检查替代术语的乘积是否小于零。
  4. 相应地增加结果。如果在遍历时发现该数组的产品大于零,则在这种情况下存储结果并再次开始计算差异数组中元素的其余部分。
  5. 查找其中的最大值将其存储到结果中,并return (result+1)
  6. 这是它在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;
    }
    

答案 4 :(得分: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循环。