web-dev-qa-db-ja.com

動的計画法:ジグザグである最長のサブシーケンスを見つけます

http://www.topcoder.com/stat?c=problem_statement&pm=1259&rd=449 で言及されている問題の解決策の背後にあるコアロジックを理解するのを誰かが助けてくれますか?

ジグザグシーケンスは、交互に増加および減少するシーケンスです。したがって、1 3 2はジグザグですが、1 23はそうではありません。 1つまたは2つの要素の任意のシーケンスはジグザグです。与えられたシーケンスの中で最長のジグザグサブシーケンスを見つける必要があります。サブシーケンスとは、最長増加部分列問題のように、要素が連続している必要がないことを意味します。したがって、1 3 5 4 2は、ジグザグのサブシーケンスとして1 54を持つことができます。一番長いものに興味があります。

これは動的計画法の問題であり、 動的計画法を使用して最も長く増加するサブシーケンスを決定する方法 と非常によく似ていることを理解しています。

どのソリューションでも、異なる長さのシーケンスを反復する外側のループが必要であり、内側のループはすべてのシーケンスを反復する必要があると思います。

インデックスiで終わる最長のジグザグシーケンスを別の配列に格納します。たとえば、dpStoreをインデックスiに格納します。したがって、中間結果は保存され、後で再利用できます。この部分は、すべての動的計画問題に共通です。後で、グローバル最大値を見つけて返します。

私の解決策は間違いなく間違っています。ここに貼り付けて、これまでの結果を示します。どこが間違っていたのか知​​りたい。

    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; 
}
16

これはあなたがリンクした問題が言っていることです:

連続する数字の差が正と負の間で厳密に交互になる場合、数字のシーケンスはジグザグシーケンスと呼ばれます。最初の違い(存在する場合)は、正または負のいずれかになります。要素が2つ未満のシーケンスは、簡単にジグザグシーケンスです。

たとえば、1,7,4,9,2,5は、差(6、-3,5、-7,3)が交互に正と負であるため、ジグザグシーケンスです。対照的に、1,4,7,2,5と1,7,4,5,5はジグザグシーケンスではありません。最初の2つの差が正であるため、2番目の差がゼロであるためです。

整数のシーケンスであるsequenceが与えられた場合、ジグザグシーケンスであるシーケンスの最長のサブシーケンスの長さを返します。サブシーケンスは、元のシーケンスからいくつかの要素(場合によってはゼロ)を削除し、残りの要素を元の順序のままにすることで取得されます。

これはあなたがあなたの投稿で説明したものとは完全に異なります。以下は、実際のトップコーダーの問題を解決します。

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配列の最大値を取得します。

50
IVlad

これはより簡単な解決策です

元の配列Aの長さをnとします。 0と1のみの長さn-1の別の配列Bを作成します。 a [i] -a [i + 1]> = 0の場合はB [i] = 0、それ以外の場合はB [i] = 1。これはO(n)で実行できます。これで、0と1のみの配列ができました。問題は、連続する0と1を交互に見つけることです。 Bが0の連続サブ配列配列は、その要素のいずれか1つで表されます。例:Bが= [0,0,0,0,0、1,0,0,0,1,0,1,1,1,0]の場合、BをBrに減らすことができます。これは= [0、 O(n)の1,0,1,0,1,0]、実際には、1回の反復で実行できるBrのサイズを見つける必要があります。そして、私の友人が与えられた問題への答えです。したがって、全体の複雑さはO(n) + O(n) = O(n))です。シーケンスの一部を拡大または縮小し、これらすべてのシーケンスの最後の要素を保持します。

更新:リストの長さではなくジグザグを数えているので、このプロセスから出てくる答えに1つ追加する必要があります。フェンスポストの問題に注意してください: https://betterexplained.com/articles/learning-how-to-count-avoiding-the-fencepost-problem/

27
surya

貪欲なアプローチもあります。

最初の要素を取ります。次に、最初の要素を含む連続したシーケンスの最小要素または最大要素を見つけて選択します。

つまり、シーケンスが1, 5, 7, 9, 2,4の場合、最初に1を選択し、次に9を選択します。これは、連続するシーケンス1, 5, 7, 9の最大値が9であるためです。

同じ方法で続行し、2と5を選択します。同じアプローチを使用して、例のサブシーケンスを計算します。

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

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

6
user434345

または、欲張りアルゴリズムを使用できます

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);
}
2
Aydar Omurbekov

実は最高得点の答えが正しいと思います(IVladの)。しかし、動的計画法の部分(外側のループ)はnot必要であると確信しています。

欲張りアプローチが使用され、操作によって_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] = 1_および_negative_end_seq[0] = 1_、すべてのiの両方の配列には、最長のサブシーケンスの長さが含まれ、pos/negはi番目の要素で終わります。 _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);
    } 
_
2
The_Ham

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;

}

0
jchopra
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;
}
0
vishpat

これは、単純な貪欲な実装に対する私の見解です。

他の人が以前に述べたように、あなたは単に最後の3つのポイントのザグを見る必要があります。

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
0
Thomas Ahle

これが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);
}
0
Sandip