动态编程:仅使用一个dp数组找到最长的zig zag子序列

时间:2014-02-21 15:04:37

标签: algorithm dynamic-programming

只使用一个dp阵列可以解决这个问题吗? 这是topcoder(http://community.topcoder.com/stat?c=problem_statement&pm=1259&rd=4493)的曲折问题 如果连续数字之间的差异在正数和负数之间严格交替,则数字序列称为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字形序列。通过从原始序列中删除一些元素(可能为零)来获得子序列,将剩余的元素保留为原始顺序。

4 个答案:

答案 0 :(得分:0)

供参考:具有两个数组的DP使用数组A [1..n],其中A [i]是以元素i上的zig结尾的之字形序列的最大长度,以及数组B [1]其中B [i]是以元素i上的zag结尾的之字形序列的最大长度。对于i从1到n,该DP使用A数组的先前条目来计算B [i],并使用B数组的先前条目来计算A [i]。以额外循环为代价,可以根据需要重新创建B条目,因此只使用A数组。不过,我不确定这是否能解决你的问题。

(另外,由于输入数组太短,因此有许多编码技巧都不值得一提。)

答案 1 :(得分:0)

这是一次尝试,我会从你的曲折处返回指数。在你的第二个输入(1,4,7,2,5)中,它返回5和4的索引,因为它是从4,7,2,5开始的锯齿形。

您可以根据结果判断整个阵列是否为锯齿形。

 public class LongestZigZag
    {
        private readonly int[] _input;

        public LongestZigZag(int[] input)
        {
            _input = input;
        }

        public Tuple<int,int> Sequence()
        {
            var indices = new Tuple<int, int>(int.MinValue, int.MinValue);

            if (_input.Length <= 2) return indices;

            for (int i = 2; i < _input.Length; i++)
            {
                var firstDiff = _input[i - 1] - _input[i - 2];
                var secondDiff = _input[i] - _input[i - 1];

                if ((firstDiff > 0 && secondDiff < 0) || (firstDiff < 0 && secondDiff > 0))
                {
                    var index1 = indices.Item1;
                    if (index1 == int.MinValue)
                    {
                        index1 = i - 2;
                    }

                    indices = new Tuple<int, int>(index1, i);
                }
                else
                {
                    indices = new Tuple<int, int>(int.MinValue, int.MinValue); 
                }
            }

            return indices;
        }
    }

答案 2 :(得分:0)

动态编程需要O(n 2 )时间来运行程序。我设计了一个线性时间复杂度为O(n)的代码。只需一次进入数组,它就会给出最大可能序列的长度。我已针对该问题测试了不同站点提供的许多测试用例,并取得了积极成果。

这是我的代码C实现:

#include <stdio.h>
#include <stdlib.h>

int main()
{
int i,j;
int n;
int count=0;
int flag=0;
scanf(" %d",&n);
int *a;
a = (int*)malloc(n*sizeof(a));

for(i=0;i<n;i++)
{
    scanf(" %d",&a[i]);  //1,7,5,10,13,15,10,5,16,8
}   
i=0; 
if(a[0] < a[1])
{
    count++;
    while(a[i] <= a[i+1] && i<n-1)
    i++;

    if(i==n-1 && a[i-1]<a[i])
    {
        count++;
        i++;
    }    
}  
  while(i<n-1)
  {   count++;
      while(a[i] >= a[i+1] && i<n-1) 
      {
        i++;  
      }
      if(i==n-1 && a[i-1]>a[i])
      {
          count++; 
          break;
      }      
      if(i<n-1)
      count++;
      while(a[i] <= a[i+1] && i<n-1)
      {
          i++;
      } 
      if(i==n-1 && a[i-1]<a[i])
      {
          count++;
          break;
      }         
  }      

printf("%d",count);
return 0;
}     

答案 3 :(得分:0)

每一个(据我所知,所以不要理所当然)你用动态编程解决的解决方案归结为代表一个“解决方案空间”(意味着每个可能的解决方案都是正确的,不一定 DAG (有向无环图)。

例如,如果您正在寻找最长的上升子序列,那么解决方案空间可以表示为以下DAG:

  • 节点标有序列号
  • 两个节点之间的边e(u, v)表示valueOf(u) < valueOf(v)(其中valueOf(x)是与节点x关联的值)

在动态编程中,找到问题的最佳解决方案与以正确的方式遍历此图表是一回事。该图提供的信息在某种意义上由该DP阵列表示。

在这种情况下,我们有两个订购操作。如果我们在其中一个图表上显示它们,那么该图形将不是非循环的 - 我们将需要至少两个图形(一个代表<关系,一个代表>)。

如果拓扑排序需要两个DAG,那么解决方案将需要两个DP阵列,或者一些巧妙的方式来指示你的DAG中的哪个边缘对应于哪个排序操作(在我看来这不必要地使问题复杂化)。

因此没有,你不能只使用一个DP阵列。您至少需要两个。至少如果你想要一个纯粹通过动态编程接近的简单解决方案。

这个问题的递归调用看起来应该是这样的(关系的方向可能是错误的,我还没有检查过):

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) + 1 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) + 1 if A[i] > A[j] for every 0 <= j < i)

这应该是O(n)具有正确的记忆(两个DP阵列)。这些调用返回解决方案的长度 - 只要找到最大值,就可以通过存储“父指针”来找到实际结果,然后在这些指针上向后遍历。