【问题标题】:Dynamic programming: Find longest subsequence that is zig zag动态规划:找到最长的子序列,即之字形
【发布时间】:2011-10-18 09:39:10
【问题描述】:

谁能帮我理解http://www.topcoder.com/stat?c=problem_statement&pm=1259&rd=4493中提到的问题的解决方案背后的核心逻辑

之字形序列是交替增加和减少的序列。所以,1 3 2 是之字形,但 1 2 3 不是。任何一个或两个元素的序列都是曲折的。我们需要找到给定序列中最长的 zig zag 子序列。子序列意味着元素不必是连续的,就像在最长递增子序列问题中一样。因此, 1 3 5 4 2 可以将 1 5 4 作为 zig zag 子序列。我们对最长的感兴趣。

我了解这是一个动态规划问题,与How to determine the longest increasing subsequence using dynamic programming?非常相似。

我认为任何解决方案都需要一个迭代不同长度序列的外循环,而内循环必须迭代所有序列。

我们将在另一个数组中存储以索引 i 结尾的最长 zig zag 序列,例如索引 i 处的 dpStore。因此,中间结果被存储起来,以后可以重用。这部分对所有动态规划问题都是通用的。稍后我们找到全局最大值并将其返回。

我的解决方案肯定是错误的,在这里粘贴以显示我到目前为止的内容。我想知道我哪里出错了。

    private int isZigzag(int[] arr)
{
    int max=0;
    int maxLength=-100;
    int[] dpStore = new int[arr.length];

    dpStore[0]=1;

    if(arr.length==1)
    {
        return 1;
    }
    else if(arr.length==2)
    {
        return 2;
    }
    else 
    {           
        for(int i=3; i<arr.length;i++)
        {
            maxLength=-100;
            for(int j=1;j<i && j+1<=arr.length; j++)
            {
                if(( arr[j]>arr[j-1] && arr[j]>arr[j+1])
                    ||(arr[j]<arr[j-1] && arr[j]<arr[j+1]))
                {
                    maxLength = Math.max(dpStore[j]+1, maxLength);
                }
            }
            dpStore[i]=maxLength;               
        }
    }
    max=-1000;
    for(int i=0;i<arr.length;i++)
    {
        max=Math.max(dpStore[i],max);
    }
    return max; 
}

【问题讨论】:

  • 您的第一个链接需要注册才能访问。如果问题描述包含在您的问题中,则回答起来会容易得多。
  • 很抱歉我没有注意到...我会快速说明问题。
  • 你也知道如何解决基本的最长递增子序列(没有锯齿形)吗?这只是对它的一个小修改,使用相同的技术来解决。
  • 1 3 5 4 2中,整个序列是zig-zag。您没有提到应该如何处理相等的数字,但不包括相等的数字,并不是所有不增加或减少的序列(这些序列也没有锯齿形子序列,除了微不足道的 1 或 2 元素序列)。那么,1 1 1 是增加还是减少?
  • 嗯,您链接到的问题与您所描述的完全不同。你能决定哪一个你需要帮助吗?

标签: algorithm dynamic-programming


【解决方案1】:

这就是你链接到的问题所说的:

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

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

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

这与您在帖子中描述的完全不同。下面解决实际的topcoder问题。

dp[i, 0] = maximum length subsequence ending at i such that the difference between the
           last two elements is positive
dp[i, 1] = same, but difference between the last two is negative

for i = 0 to n do     
   dp[i, 0] = dp[i, 1] = 1

   for j = 0 to to i - 1 do
    if a[i] - a[j] > 0
      dp[i, 0] = max(dp[j, 1] + 1, dp[i, 0])
    else if a[i] - a[j] < 0
      dp[i, 1] = max(dp[j, 0] + 1, dp[i, 1])
    

例子:

i        = 0  1   2  3   4   5   6   7  8   9
a        = 1  17  5  10  13  15  10  5  16  8 
dp[i, 0] = 1  2   2  4   4   4   4   2  6   6    
dp[i, 1] = 1  1   3  3   3   3   5   5  3   7
           ^  ^   ^  ^
           |  |   |  -- gives us the sequence {1, 17, 5, 10}
           |  |   -- dp[2, 1] = dp[1, 0] + 1 because 5 - 17 < 0.
           |  ---- dp[1, 0] = max(dp[0, 1] + 1, 1) = 2 because 17 - 1 > 0
     1 element
   nothing to do
 the subsequence giving 7 is 1, 17, 5, 10, 5, 16, 8, hope I didn't make any careless
 mistakes in computing the other values)

然后取两个 dp 数组的最大值。

【讨论】:

  • 这里的dp 是什么?是2行n列的矩阵吗?
  • dp[i][1] 对应的值 13 和 15 根据定义应该是 3 而不是 2。
  • 嗨@IVlad,在计算出7是最大子数组的长度后,你是如何获得实际的7个元素的?
  • 这个问题也有线性解,看下面的另一个答案。
  • 你能写出这个算法的递归关系吗
【解决方案2】:

还有一种贪婪的方法。

取第一个元素。然后找出包括第一个元素在内的连续序列中的最小或最大元素并选择它。

即如果序列为1, 5, 7, 9, 2,4,先选择1,再选择9,因为9是连续序列1, 5, 7, 9中的最大值。

以同样的方式继续并选择 2 和 5。 使用相同的方法,为示例计算的子序列:

1, 17, 5, 10, 13, 15, 10, 5, 16, 8

是:1, 17, 5, 15, 5, 16, 8

【讨论】:

  • 我们如何找到下一个更大或下一个更小的?
  • 这种贪婪的方法能否将复杂度降低到 O(n) >
【解决方案3】:

这是一个更简单的解决方案

设原始数组 A 的长度为 n。构建另一个长度为 n-1 且只有 0 和 1 的数组 B。 B[i] = 0 如果 a[i]-a[i+1] >=0 否则 B[i] = 1。这可以在 O(n) 中完成。现在我们有一个只有 0 和 1 的数组,现在的问题是找到交替连续的 0 和 1。 B中的连续子数组数组将由其任何一个元素表示。例如: 如果 B = [0,0,0,0,0, 1,0,0,0,1,0,1,1,1,0] 那么我们可以将 B 简化为 Br = [0,1,0 ,1,0,1,0] 在 O(n) 中,实际上我们只需要找到 Br 的大小,只需一次迭代即可完成。我的朋友就是给定问题的答案。所以总复杂度是 O(n) + O(n) = O(n)。 换一种说法: 保留第一个元素。然后找到序列中单调增长或收缩的部分,并保留所有这些序列中的最后一个元素。

更新:您需要在此过程中得出的答案中添加一个,因为您计算的是曲折,而不是列表的长度。当心围栏帖子问题:https://betterexplained.com/articles/learning-how-to-count-avoiding-the-fencepost-problem/

【讨论】:

  • 你能证明这个算法的最优性吗?
  • Olayinka:如果必须读取长度为 n 的数组的每个元素,就不可能有比 O(n) 更好的算法。
  • @surya 你的解决方案不适用于1 17 5 10 13 15 10 5 16 8 这里b 变成[1 0 1 1 1 0 0 1 0]br 将变成[1 0 1 0 1 0] 所以你的算法给6 但答案是7
  • @EmptyData 纠正我如果我错了,但我认为你需要在br 的长度上加 1。毕竟,如果整个数组是曲折的,br 将只有n-1 元素。想想a = [1,2]b=br=[1] 的情况。也就是说,这个算法似乎对我有用。还有其他反例吗?
  • 我认为这是一个更简洁的解决方案 - cs.stackexchange.com/q/75063。我们不需要Br(额外空间)
【解决方案4】:

定义之字折线(tup):

length = len(tup)
lst = []
lst.append(1)
lst.append(2)
if length > 2:
    for i in range(2,length):
        if (tup[i]-tup[i-1]) * (tup[i-1]-tup[i-2]) < 0:
            d = lst[i-1] + 1
        else:
            d = lst[i-1]
        lst.append(d)

return lst[length-1]

【讨论】:

  • 这里只计算zigzag的连续序列。
【解决方案5】:

或者你可以使用贪心算法

public static int longestZigZag(int[] sequence) {
    if (sequence.length==1) return 1;
    if (sequence.length==2) return 2;
    int[] diff = new int[sequence.length-1];

    for (int i=1;i<sequence.length;i++){
        diff[i-1]=sequence[i]-sequence[i-1];
    }
    int prevsign=sign(diff[0]);
    int count=0;
    if (prevsign!=0)
        count=1;
    for (int i=1;i<diff.length;i++){
        int sign=sign(diff[i]);
        if (prevsign*sign==-1){
            prevsign=sign;
            count++;
        }
    }
    return count+1;
}

public static int sign(int a){
    if (a==0) return 0;
    return a/Math.abs(a);
}

【讨论】:

    【解决方案6】:
    public static int longestZigZag(int[] sequence) {
        int max_seq = 0;
    
        if (sequence.length == 1) {
            return 1;
        }
    
        if (sequence.length == 1) {
            return 2;
        }
    
        int dp[] = new int[sequence.length];
    
        dp[0] = 1;
        dp[1] = 2;
    
        for (int i = 2; i < sequence.length; i++) {
            for (int j = i - 1; j > 0; j--) {
                if (((sequence[i] > sequence[j] &&
                    sequence[j] < sequence[j - 1]) || 
                    (sequence[i] < sequence[j] &&
                    sequence[j] > sequence[j - 1])) &&
                    dp[i] < dp[j] + 1) {
                    dp[i] = dp[j] + 1;
    
                    if (dp[i] > max_seq) {
                        max_seq = dp[i];
                    }
                } 
            }
        }
    
        return max_seq;
    }
    

    【讨论】:

      【解决方案7】:

      这是我对一个简单的贪婪实现的看法。

      就像其他人之前提到的那样,您只需要查看最后三个点的 zag'ing。

      def zigzag(xs):
          res = xs[:2]
          for x in xs[2:]:
              if cmp(res[-1], x) == cmp(res[-1], res[-2]):
                  res.append(x)
              else:
                  res[-1] = x
          return res
      

      【讨论】:

        【解决方案8】:

        其实我认为得分最高的答案是正确的(IVlad's)。但我很确定动态编程部分(外循环)不是必要的。

        采用贪心的方式,我们可以通过运算得到positive_end_seq[i]negative_end_seq[i]

            positive_end_seq[i] = negative_end_seq[i-1];
            negative_end_seq[i] = positive_end_seq[i-1];
            if (A[i-1] > A[i]) { // next element for positive_end_seq
               positive_end_seq[i] += 1; 
            }
            if (A[i-1] < A[i]) { // next element for negqtive_end_seq
               negative_end_seq[i] += 1;
            }
            // if (A[i-1] == A[i]) values don't change
        

        positive_end_seq[0] = 1negative_end_seq[0] = 1,所有i 的两个数组都包含最长子序列的长度,pos/neg 以i-th 元素结尾。我们不必查看 0..i-2 元素,如果能证明这一点就好了。

        时间复杂度为O(n)

        当然,pos/neg 数组现在可以用计数器代替了,这里是 Java 代码

            public static int subZigZag(int[] arr) {
              int pos_count = 1;
              int neg_count = 1;
              for(int i = 1; i < arr.length; ++i) {
                if (arr[i-1] < arr[i]) {
                  pos_count = neg_count + 1;
                }
                if (arr[i-1] > arr[i]) {
                  neg_count = pos_count+1;
                }
              }
              return Math.max(pos_count, neg_count);
            } 
        

        【讨论】:

          【解决方案9】:

          这是在 O(n) 中是如何做到的

          public static int longestZigZag(int[] sequence) {
              if (sequence == null) {
                  return 0;
              }
          
              int len  = sequence.length;
              if (len <= 2) {
                  return len;
              }
              int minima = sequence[0];
              int maxima = sequence[0];
              int maximalen = 1;
              int minimalen = 1;
          
              for (int i = 1; i < len; i++) {
                  if (sequence[i] < maxima) {
                      if (minimalen < maximalen + 1) {
                          minimalen = maximalen + 1;
                          minima = sequence[i];
                      } else if (minimalen == maximalen + 1 && sequence[i] < minima) {
                          minima = sequence[i];
                      }
                  }
                  if (sequence[i] > minima) {
                      if (maximalen < minimalen + 1) {
                          maximalen = minimalen + 1;
                          maxima = sequence[i];
                      } else if (maximalen == minimalen + 1 && sequence[i] > maxima) {
                          maxima = sequence[i];
                      }
                  }
              }
          
              return Math.max(maximalen, minimalen);
          }
          

          【讨论】:

            【解决方案10】:

            选择局部最大值和局部最小值,很简单。

            vector<int> longest_oscilating_subsequence(const vector<int> seq) {
                vector<int> result; // the resulting subsequence 
            
                for (int i = 0; i < seq.size(); ++i) {
                    if (i > 0 && seq[i] == seq[i - 1]) continue;
            
                    // is this point a local extreme 
                    bool local_max = true, local_min = true;
                    if (i > 0) {
                        local_max = local_max && (seq[i] >= seq[i - 1]);
                        local_min = local_min && (seq[i] <= seq[i - 1]);
                    }
                    if (i < seq.size() - 1) {
                        local_max = local_max && (seq[i] >= seq[i + 1]);
                        local_min = local_min && (seq[i] <= seq[i + 1]);
                    }
            
                    // potentially add it to the sequence 
                    if (local_max || local_min) result.push_back(seq[i]);
                }
            
                return result; 
            }
            

            【讨论】:

              【解决方案11】:

              int zigzag(int[] a) {

              List<Integer> list= new ArrayList<>();
              int max = 0;
              if(a.length==0 || a.length==1) return 0;
              if(a.length==2) return 1;
              for(int i=1;i<a.length-1;i++){
              
                  if((a[i-1]<a[i] && a[i+1]<a[i]) || (a[i-1]>a[i] && a[i+1]>a[i])){
                      if(list.isEmpty()){
                         list.add(a[i-1]); 
                      }
                      list.add(a[i]);
              
                  }else{
                      list.add(a[i+1]); 
                      max = Math.max(max,list.size());
                      list.clear();
                  }
              
              }
              return max;
              

              }

              【讨论】:

              • 你想解释一下你的答案吗?
              猜你喜欢
              • 1970-01-01
              • 2017-04-26
              • 2019-05-28
              • 2016-05-02
              • 2015-05-17
              • 2011-02-06
              • 2015-04-29
              • 2016-08-12
              • 1970-01-01
              相关资源
              最近更新 更多