【问题标题】:Dynamic programming: Find longest subsequence that is zig zag using only one dp array动态编程:仅使用一个 dp 数组查找最长的 zig zag 子序列
【发布时间】:2014-03-23 04:09:45
【问题描述】:

这个问题可以只使用一个 dp 数组来解决吗? 这是来自 topcoder (http://community.topcoder.com/stat?c=problem_statement&pm=1259&rd=4493) 的曲折问题 如果连续数字之间的差异在正负之间严格交替,则数字序列称为之字形序列。第一个差异(如果存在)可以是正面的也可以是负面的。少于两个元素的序列通常是锯齿形序列。

例如,1,7,4,9,2,5 是锯齿形序列,因为差值 (6,-3,5,-7,3) 交替出现正负。相比之下,1,4,7,2,5 和 1,7,4,5,5 不是 zig-zag 序列,第一个是因为它的前两个差是正数,第二个是因为它的最后一个差是零。

给定一个整数序列,sequence,返回该序列的最长子序列的长度,即锯齿形序列。子序列是通过从原始序列中删除一些元素(可能为零)而获得的,其余元素保持原始顺序。

【问题讨论】:

  • 所需的运行时间是多少?
  • 你到底想要什么?如果您只想要一个数组,只需使用长度为 2n 的数组,其中 0-n 为 zag,n-2n 为 zig?

标签: algorithm dynamic-programming


【解决方案1】:

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

(另外,由于输入数组很短,有很多编码技巧不值一提。)

【讨论】:

    【解决方案2】:

    这是一个尝试,我正在从您有锯齿形的地方返回索引。在您的第二个输入 (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;
            }
        }
    

    【讨论】:

      【解决方案3】:

      动态编程需要 O(n2) 时间来运行一个程序。我设计了一个线性时间复杂度 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;
      }     
      

      【讨论】:

      • 动态编程需要 O(n2) - 不,它不需要。
      【解决方案4】:

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

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

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

      在动态规划中,找到问题的最优解与以正确的方式遍历此图是一回事。该图提供的信息在某种意义上由该 DP 数组表示。

      在这种情况下,我们有两个排序操作。如果我们将它们都显示在其中一个这样的图上,那么该图将不是非循环的 - 我们将需要至少两个图(一个代表&lt; 关系,一个代表&gt;)。

      如果拓扑排序需要两个 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 数组)。这些调用返回解决方案的长度——只要找到最大值,就可以通过存储“父指针”找到实际结果,然后在这些指针上向后遍历。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-03
        相关资源
        最近更新 更多