数组中最长的凸子序列

时间:2013-12-18 17:45:06

标签: algorithm

假设我们给出了一个整数输入数组,如何找到满足以下条件的最长凸子序列:

c[i] < (c[i-1] + c[i+1]) / 2

c[i-1]c[i]c[i+1]是子序列中的三个连续元素。

例如,如果输入数组为{ 1, 2, -1, 0, 3, 8, 5 },则最长的凸子序列应为:{ 1, -1, 0, 3, 8 }{ 2, -1, 0, 3, 8 }

我尝试使用“最长增加子序列”(LIS)问题中的相同动态编程思想来解决此问题。但是因为子序列中的每个元素都依赖于前两个元素,所以似乎O(n ^ 2)解决方案是不可能的。谢谢您的帮助。

2 个答案:

答案 0 :(得分:4)

这是O(N 2 log N)算法(由于log N只是因为排序我们可以将其减少为O(N 2 使用不同的排序算法或更高级的优先级队列来记录日志N)或甚至O(N 2 ):

  1. 为每对输入数组元素创建一个数组:P[]。每对内的元素按其索引排序。
  2. 根据值差Y2 - Y1对这些对进行排序。如果值相等Y2 - Y1,则应按降序排列第二个索引。
  3. 对索引为0 ... N-1的子序列的子序列长度进行零初始化L[]
  4. 对于来自P[]的每对(按排序顺序):L[X2] = max(L[X2], L[X1] + 1)。其中X是输入数组中元素的索引。
  5. L[]中找到最大值。这是最长凸子序列的长度。
  6. 为了能够重建子序列本身,步骤#4还应该更新后向指针链。更新L[X2]后,我们将创建一个节点,该节点指向由X1对应的条目指向的节点,然后将与X2对应的条目指向此新节点:BP_Head[X2] = new Node(BP_Head[X1])
  7. 凸性属性c[i] < (c[i-1] + c[i+1]) / 2可能会转换为等效的不等式c[i] - c[i-1] < c[i+1] - c[i]。这意味着在按排序顺序处理对时,我们不再需要检查凸属性。所以第4步的唯一任务就是增加子串。

    该算法的简化变体需要O(N 2 )空间。如果不使用大型数组P[],我们会使用输入数组S[]的预排序副本以及优先级队列来减少空间复杂性。步骤#4从该优先级队列的顶部接收元素。为了保持此优先级队列的大小等于N,我们可以仅在删除S[i+1] - S[j]之后将元素S[i] - S[j]推送到队列(因此​​队列仅为每个j保留一个元素)。如果我们使用已知的DP技巧来存储指向原始后向指针链的“中间”的一个后向指针(对于每个索引)(然后递归地重复该算法),则不需要后向指针林消耗的巨大空间对于这个“中间”后指针之前和之后的两个子数组。


    和O(N 3 )算法:

    1. 构造图,其中每个顶点对应于(按索引排序)数组元素对。
    2. 如果顶点有一个公共数组元素,它位于与这些顶点关联的剩余两个元素之间,并且所有三个元素都满足凸属性,则将它们连接到边。该边缘应该指向具有较大索引的顶点。
    3. 添加源节点和目标节点并将它们连接到每个顶点。
    4. 在此图表中找到最长路径。
    5. 该图具有O(N 2 )顶点和O(N 3 )边。它可以用O(N 3 )时间构建;由于它是一个DAG,找到最长路径也需要O(N 3 )时间。

答案 1 :(得分:4)

让我们将最长的凸序列称为LCS; N> 1的最小可能长度是2.下面的算法是不言自明的。

int LCS[N];
LCS[0] = 1; LCS[1] =2 ;

for(i=2;i<N;i++)
{
   LCS[i] = 2;
   for(j=1;j<i;j++)
   {
       for(k=0;k<j;k++)
       {
           if(LCS[j]-1 == LCS[k] && A[j] < (A[k] + A[i])/2 )
               LCS[i] = max( LCS[i], 1+LCS[j]);
       }
   }
}